├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── hqumath │ │ └── androidmvvm │ │ ├── adapter │ │ ├── MyFragmentPagerAdapter.java │ │ └── MyRecyclerAdapters.java │ │ ├── app │ │ ├── App.java │ │ ├── AppExecutors.java │ │ ├── AppManager.java │ │ ├── Constant.java │ │ └── CrashHandler.java │ │ ├── base │ │ ├── BaseActivity.java │ │ ├── BaseFragment.java │ │ ├── BaseModel.java │ │ ├── BaseRecyclerAdapter.java │ │ ├── BaseRecyclerViewHolder.java │ │ └── BaseViewModel.java │ │ ├── bean │ │ ├── BaseResultEntity.java │ │ ├── CommitEntity.java │ │ ├── ReposEntity.java │ │ └── UserInfoEntity.java │ │ ├── net │ │ ├── ApiService.java │ │ ├── CreateRequestBodyUtil.java │ │ ├── HandlerException.java │ │ ├── HttpListener.java │ │ ├── LogInterceptor.java │ │ ├── RetrofitClient.java │ │ ├── TokenException.java │ │ ├── download │ │ │ ├── DownloadInterceptor.java │ │ │ ├── DownloadListener.java │ │ │ └── DownloadResponseBody.java │ │ └── upload │ │ │ ├── ProgressRequestBody.java │ │ │ └── UploadProgressListener.java │ │ ├── repository │ │ ├── AppDatabase.java │ │ ├── MyModel.java │ │ └── dao │ │ │ └── UserInfoDao.java │ │ ├── service │ │ └── UpdateService.java │ │ ├── ui │ │ ├── fileupdown │ │ │ ├── FileUpDownActivity.java │ │ │ └── FileUpDownViewModel.java │ │ ├── follow │ │ │ ├── FollowersFragment.java │ │ │ ├── FollowersViewModel.java │ │ │ ├── FollowersViewModelOld.java │ │ │ ├── ProfileDetailActivity.java │ │ │ └── ProfileDetailViewModel.java │ │ ├── login │ │ │ ├── LoginActivity.java │ │ │ └── LoginViewModel.java │ │ ├── main │ │ │ ├── AboutFragment.java │ │ │ ├── MainActivity.java │ │ │ └── SettingsFragment.java │ │ └── repos │ │ │ ├── MyReposFragment.java │ │ │ ├── ReposDetailActivity.java │ │ │ ├── ReposDetailViewModel.java │ │ │ ├── ReposFragment.java │ │ │ ├── ReposViewModel.java │ │ │ └── StarredFragment.java │ │ ├── utils │ │ ├── ByteUtil.java │ │ ├── CommonUtil.java │ │ ├── Density.java │ │ ├── DeviceUtil.java │ │ ├── DialogUtil.java │ │ ├── FileUtil.java │ │ ├── LogUtil.java │ │ ├── PermissionUtil.java │ │ ├── SPUtil.java │ │ ├── SSLSocketClient.java │ │ ├── SignatureUtil.java │ │ ├── StringUtil.java │ │ └── ZxingUtil.java │ │ └── widget │ │ ├── DownloadingDialog.java │ │ └── DownloadingProgressBar.java │ └── res │ ├── anim │ ├── in_from_left.xml │ ├── in_from_right.xml │ ├── out_to_left.xml │ ├── out_to_right.xml │ ├── push_bottom_in.xml │ └── push_bottom_out.xml │ ├── drawable │ ├── bg_button_login.xml │ ├── bottom_navigation_item_selector.xml │ ├── dialog_common_bg.xml │ ├── ic_code.xml │ ├── ic_custom.png │ ├── ic_empty.xml │ ├── ic_github.xml │ ├── ic_group_secondary.xml │ ├── ic_issues.xml │ ├── ic_link_secondary.xml │ ├── ic_logout.xml │ ├── ic_mail.xml │ ├── ic_mail_secondary.xml │ ├── ic_menu_about.xml │ ├── ic_menu_person.xml │ ├── ic_menu_repo.xml │ ├── ic_menu_settings.xml │ ├── ic_menu_star.xml │ ├── ic_menu_trace.xml │ └── ic_style.png │ ├── layout │ ├── activity_fileupdown.xml │ ├── activity_login.xml │ ├── activity_main.xml │ ├── activity_profile_detail.xml │ ├── activity_repos_detail.xml │ ├── dialog_common.xml │ ├── dialog_downloading.xml │ ├── fragment_about.xml │ ├── fragment_followers.xml │ ├── fragment_repos.xml │ ├── fragment_settings.xml │ ├── fragment_swipe_list.xml │ ├── recycler_item_commits.xml │ ├── recycler_item_followers.xml │ ├── recycler_item_repos.xml │ ├── recycler_layout_empty.xml │ └── recycler_layout_empty_databinding.xml │ ├── menu │ ├── main_bottom_navigation.xml │ └── menu_navigation.xml │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values │ ├── anim.xml │ ├── attrs.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── file_paths.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── architecture.png ├── fc.png └── paging.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.gradle 3 | /build 4 | /captures 5 | /local.properties 6 | /*.iml 7 | /*.rar 8 | /.DS_Store 9 | /*.apk 10 | /.externalNativeBuild -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidMVVM 2 | Android MVVM是一款基于MVVM框架,以Jetpack组件DataBinding+LiveData+ViewModel为基础,整合Retrofit+RxJava网络模块的快速开发框架。 3 | 4 | ## 框架流程 5 | ![](./img/fc.png) 6 | ![](./img/architecture.png) 7 | 8 | ## 框架特点 9 | - **Jetpack组件** 10 | 11 | 1. ViewBinding & DataBinding 12 | 2. Lifecycles 13 | 3. LiveData 14 | 4. Navigation 15 | 5. Paging 16 | 6. Room 17 | 7. ViewModel 18 | 19 | - **流行框架** 20 | 21 | 1. [retrofit](https://github.com/square/retrofit)+[okhttp](https://github.com/square/okhttp)+[rxJava](https://github.com/ReactiveX/RxJava)负责网络请求 22 | 2. [gson](https://github.com/google/gson)负责解析json数据 23 | 3. [glide](https://github.com/bumptech/glide)负责加载图片; 24 | 25 | - **基类封装** 26 | 27 | 1. BaseActivity 28 | 2. BaseFragment 29 | 3. BaseViewModel 30 | 31 | - **全局操作** 32 | 33 | 1. 全局的Activity堆栈式管理 34 | 2. LoggingInterceptor全局拦截网络请求日志 35 | 3. 全局的异常捕获,程序发生异常时不会崩溃,返回上个界面。 36 | 4. 使用androidx 37 | 5. 不使用kotlin 38 | 39 | - **Room组件** 40 | 41 | 1. 实现了Network only 和 Network & database 两种模式 42 | 43 | FollowersFragment 使用 Room 持久化存储列表数据,Network => DB => LiveData => RecyclerView 44 | 45 | ![](./img/paging.png) 46 | 47 | ## 界面 48 | 49 | 1. 登录界面(使用任意账户登录) 50 | 2. 我的仓库列表 51 | 3. 我的star仓库列表 52 | 4. 我的following列表 53 | 5. 仓库详情 54 | 6. 用户详情 55 | 56 | ## 注意 57 | 58 | 1. 接口使用GitHub API v3,单IP限制每小时60次requests 59 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /*.apk 3 | /*.iml 4 | /release -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.hqumath.androidmvvm' //影响R类生成 7 | compileSdk 31 8 | 9 | defaultConfig { 10 | applicationId "com.hqumath.androidmvvm" //影响AndroidManifest中package 11 | minSdk 21 12 | //noinspection ExpiredTargetSdkVersion 13 | targetSdk 31 14 | versionCode 20211020 15 | versionName "2.0" 16 | } 17 | buildFeatures { 18 | viewBinding true 19 | dataBinding true 20 | } 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | //配置自定义打包名称 32 | applicationVariants.all { variant -> 33 | variant.outputs.all { 34 | def fileName 35 | if (variant.buildType.name.equals('release')) { 36 | fileName = "MVVMDemo_${variant.mergedFlavor.versionName}_${variant.mergedFlavor.versionCode}.apk" 37 | } else if (variant.buildType.name.equals('debug')) { 38 | fileName = "MVVMDemo_${variant.mergedFlavor.versionName}_debug_${variant.mergedFlavor.versionCode}.apk" 39 | } 40 | outputFileName = fileName 41 | } 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation fileTree(dir: 'libs', include: ['*.jar']) 47 | implementation 'androidx.appcompat:appcompat:1.2.0' 48 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 49 | implementation 'androidx.cardview:cardview:1.0.0' 50 | implementation 'com.google.android.material:material:1.2.0' 51 | //lifecycle 52 | def lifecycle_version = "2.4.0-rc01" 53 | implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" 54 | implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" 55 | implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" 56 | //room 57 | def room_version = "2.3.0" 58 | implementation "androidx.room:room-runtime:$room_version" 59 | annotationProcessor "androidx.room:room-compiler:$room_version" 60 | //implementation "androidx.room:room-paging:2.4.0-beta01" 61 | //paging 2=>3 升级,大刀阔斧很多API改变了,稳定后再用吧,暂不处理显示大数据集(本地数据库和网络)的问题 62 | //def paging_version = "3.0.1" 63 | //implementation "androidx.paging:paging-runtime:$paging_version" 64 | //implementation "androidx.paging:paging-compose:1.0.0-alpha14" 65 | 66 | //rxjava 67 | implementation 'io.reactivex.rxjava2:rxjava:2.2.9' 68 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 69 | //network 70 | implementation 'com.squareup.okhttp3:okhttp:3.12.1' 71 | implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1' 72 | implementation 'com.squareup.retrofit2:retrofit:2.5.0' 73 | implementation 'com.squareup.retrofit2:converter-gson:2.4.0'//数据解析器 74 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'//网络请求适配器 75 | // MultiDex的依赖 76 | //implementation 'org.robolectric:shadows-multidex:3.4-rc2' 77 | //下拉刷新 78 | implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3' //核心必须依赖 79 | implementation 'com.scwang.smart:refresh-header-classics:2.0.3' //经典刷新头 80 | //权限获取 81 | implementation 'com.yanzhenjie:permission:2.0.3' 82 | //picture 83 | implementation 'com.github.bumptech.glide:glide:4.12.0' 84 | 85 | //内存泄漏 86 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 43 | 47 | 51 | 52 | 55 | 56 | 57 | 62 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/adapter/MyFragmentPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.adapter; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | import androidx.fragment.app.Fragment; 6 | import androidx.fragment.app.FragmentManager; 7 | import androidx.fragment.app.FragmentPagerAdapter; 8 | 9 | import com.hqumath.androidmvvm.base.BaseFragment; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * **************************************************************** 15 | * 文件名称: MyFragmentPagerAdapter 16 | * 作 者: Created by gyd 17 | * 创建时间: 2019/9/2 15:15 18 | * 文件描述: 19 | * 注意事项: 20 | * 版权声明: 21 | * **************************************************************** 22 | */ 23 | public class MyFragmentPagerAdapter extends FragmentPagerAdapter { 24 | 25 | private List fragmentList; 26 | private List titles; 27 | 28 | public MyFragmentPagerAdapter(FragmentManager fm) { 29 | super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); 30 | } 31 | 32 | public void setData(List fragmentList, List titles) { 33 | this.fragmentList = fragmentList; 34 | this.titles = titles; 35 | } 36 | 37 | 38 | @NonNull 39 | @Override 40 | public Fragment getItem(int position) { 41 | return fragmentList.get(position); 42 | } 43 | 44 | @Override 45 | public int getCount() { 46 | return fragmentList.size(); 47 | } 48 | 49 | @Nullable 50 | @Override 51 | public CharSequence getPageTitle(int position) { 52 | return titles == null ? "" : titles.get(position); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/adapter/MyRecyclerAdapters.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.adapter; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.widget.ImageView; 6 | 7 | import com.bumptech.glide.Glide; 8 | import com.hqumath.androidmvvm.R; 9 | import com.hqumath.androidmvvm.base.BaseRecyclerAdapter; 10 | import com.hqumath.androidmvvm.base.BaseRecyclerViewHolder; 11 | import com.hqumath.androidmvvm.bean.CommitEntity; 12 | import com.hqumath.androidmvvm.bean.ReposEntity; 13 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 14 | import com.hqumath.androidmvvm.utils.CommonUtil; 15 | 16 | import java.text.ParseException; 17 | import java.text.SimpleDateFormat; 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | public class MyRecyclerAdapters { 22 | 23 | //我的跟随 24 | public static class FollowRecyclerAdapter extends BaseRecyclerAdapter { 25 | public FollowRecyclerAdapter(Context context, List mData) { 26 | super(context, mData, R.layout.recycler_item_followers); 27 | } 28 | 29 | public void setData(List list) { 30 | this.mData = list; 31 | } 32 | 33 | @Override 34 | public void convert(BaseRecyclerViewHolder holder, int position) { 35 | UserInfoEntity data = mData.get(position); 36 | holder.setText(R.id.tv_name, data.getLogin()); 37 | ImageView ivHead = holder.getView(R.id.iv_head); 38 | if (!TextUtils.isEmpty(data.getAvatar_url())) { 39 | Glide.with(CommonUtil.getContext()) 40 | .load(data.getAvatar_url()) 41 | .circleCrop() 42 | .into(ivHead); 43 | } 44 | } 45 | } 46 | 47 | //我的仓库 48 | public static class ReposRecyclerAdapter extends BaseRecyclerAdapter { 49 | public ReposRecyclerAdapter(Context context, List mData) { 50 | super(context, mData, R.layout.recycler_item_repos); 51 | } 52 | 53 | @Override 54 | public void convert(BaseRecyclerViewHolder holder, int position) { 55 | ReposEntity data = mData.get(position); 56 | holder.setText(R.id.tv_name, data.getName()); 57 | holder.setText(R.id.tv_description, data.getDescription()); 58 | holder.setText(R.id.tv_author, data.getOwner().getLogin()); 59 | } 60 | } 61 | 62 | //提交记录 63 | public static class CommitsRecyclerAdapter extends BaseRecyclerAdapter { 64 | public CommitsRecyclerAdapter(Context context, List mData) { 65 | super(context, mData, R.layout.recycler_item_commits); 66 | } 67 | 68 | @Override 69 | public void convert(BaseRecyclerViewHolder holder, int position) { 70 | CommitEntity data = mData.get(position); 71 | holder.setText(R.id.tv_name, data.getCommit().getCommitter().getName()); 72 | holder.setText(R.id.tv_message, data.getCommit().getMessage()); 73 | holder.setText(R.id.tv_sha, data.getSha()); 74 | //时间格式化 75 | String date = data.getCommit().getCommitter().getDate();//2011-12-29T04:45:11Z 76 | date = date.replace("Z", " UTC");//UTC是世界标准时间 77 | SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss Z"); 78 | SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 79 | try { 80 | Date date1 = format1.parse(date); 81 | String date2 = format2.format(date1); 82 | holder.setText(R.id.tv_time, date2); 83 | } catch (ParseException e) { 84 | e.printStackTrace(); 85 | } 86 | } 87 | } 88 | } 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/app/App.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.app; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.hqumath.androidmvvm.utils.CommonUtil; 10 | import com.hqumath.androidmvvm.utils.Density; 11 | 12 | /** 13 | * **************************************************************** 14 | * 文件名称: AppApplication 15 | * 作 者: Created by gyd 16 | * 创建时间: 2019/5/31 17:04 17 | * 文件描述: 18 | * 注意事项: 19 | * 版权声明: 20 | * **************************************************************** 21 | */ 22 | public class App extends Application { 23 | //获取全局上下文 CommonUtil.getContext(); 24 | //private static Application sInstance; 25 | 26 | @Override 27 | public void onCreate() { 28 | super.onCreate(); 29 | setApplication(this); 30 | //初始化工具类 31 | CommonUtil.init(this); 32 | //屏幕适配方案,根据ui图修改,屏幕最小宽度375dp 33 | Density.setDensity(this, 375f); 34 | 35 | //异常捕获后重启,umeng等可能无法统计到异常信息 36 | //CrashHandler myCrashHandler =CrashHandler.getInstance(); 37 | //myCrashHandler.init(this); 38 | } 39 | 40 | public static synchronized void setApplication(@NonNull Application application) { 41 | //sInstance = application; 42 | //注册监听每个activity的生命周期,便于堆栈式管理 43 | application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { 44 | 45 | @Override 46 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 47 | AppManager.getInstance().addActivity(activity); 48 | } 49 | 50 | @Override 51 | public void onActivityStarted(Activity activity) { 52 | } 53 | 54 | @Override 55 | public void onActivityResumed(Activity activity) { 56 | } 57 | 58 | @Override 59 | public void onActivityPaused(Activity activity) { 60 | } 61 | 62 | @Override 63 | public void onActivityStopped(Activity activity) { 64 | } 65 | 66 | @Override 67 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 68 | } 69 | 70 | @Override 71 | public void onActivityDestroyed(Activity activity) { 72 | AppManager.getInstance().removeActivity(activity); 73 | } 74 | }); 75 | } 76 | 77 | /** 78 | * 获得当前app运行的Application 79 | */ 80 | /*public static Application getInstance() { 81 | if (sInstance == null) { 82 | throw new NullPointerException("please inherit BaseApplication or call setApplication."); 83 | } 84 | return sInstance; 85 | }*/ 86 | 87 | /*@Override 88 | protected void attachBaseContext(Context base) { 89 | super.attachBaseContext(base); 90 | MultiDex.install(this);//方法数超过65k 91 | }*/ 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/app/AppExecutors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hqumath.androidmvvm.app; 18 | 19 | import android.os.Handler; 20 | import android.os.Looper; 21 | import androidx.annotation.NonNull; 22 | 23 | import java.util.concurrent.Executor; 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | import java.util.concurrent.ScheduledExecutorService; 27 | 28 | /** 29 | * 全局线程池 30 | * Global executor pools for the whole application. 31 | * 对任务进行分组,io操作和网络请求可同时执行 32 | * 用法:AppExecutors.getInstance().driveWorkThread().execute(() -> {}); 33 | */ 34 | public class AppExecutors { 35 | 36 | //双重校验锁 37 | /*private volatile static AppExecutors sInstance; 38 | 39 | public static AppExecutors getInstance() { 40 | if (sInstance == null) { 41 | synchronized (AppExecutors.class) { 42 | if (sInstance == null) { 43 | sInstance = new AppExecutors(); 44 | } 45 | } 46 | } 47 | return sInstance; 48 | }*/ 49 | 50 | //静态内部类 51 | private static class AppExecutorsHolder { 52 | private static final AppExecutors instance = new AppExecutors(); 53 | } 54 | 55 | public static AppExecutors getInstance() { 56 | return AppExecutorsHolder.instance; 57 | } 58 | 59 | private MainThreadExecutor mainThread;//ui线程操作 60 | private ExecutorService workThread;//工作线程池,执行普通任务。例如:网络通讯和多媒体操作 61 | private ScheduledExecutorService scheduledWork;//循环线程池,执行普通循环任务。 62 | 63 | public MainThreadExecutor mainThread() { 64 | if (mainThread == null) { 65 | mainThread = new MainThreadExecutor(); 66 | } 67 | return mainThread; 68 | } 69 | 70 | public static class MainThreadExecutor implements Executor { 71 | private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); 72 | 73 | @Override 74 | public void execute(@NonNull Runnable command) { 75 | mainThreadHandler.post(command); 76 | } 77 | 78 | public void postDelayed(Runnable command, int delayMillis) { 79 | mainThreadHandler.postDelayed(command, delayMillis); 80 | } 81 | } 82 | 83 | public ExecutorService workThread() { 84 | if (workThread == null || workThread.isShutdown()) 85 | workThread = Executors.newFixedThreadPool(8);//骁龙888八个CPU核心 86 | return workThread; 87 | } 88 | 89 | public ScheduledExecutorService scheduledWork() { 90 | if (scheduledWork == null || scheduledWork.isShutdown()) 91 | scheduledWork = Executors.newScheduledThreadPool(4); 92 | return scheduledWork; 93 | } 94 | 95 | public void shutdownWorkThread() { 96 | if (workThread != null) { 97 | workThread.shutdown(); 98 | workThread = null; 99 | } 100 | } 101 | 102 | public void shutdownScheduledWork() { 103 | if (scheduledWork != null) { 104 | scheduledWork.shutdown(); 105 | scheduledWork = null; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/app/AppManager.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.app; 2 | 3 | import android.app.Activity; 4 | 5 | import java.util.Stack; 6 | 7 | /** 8 | * 栈管理 9 | * 添加、删除当前activity、删除指定的activity、清空栈、求栈大小 10 | */ 11 | public class AppManager { 12 | 13 | private static Stack activityStack = new Stack(); 14 | 15 | private static AppManager appManager = null; 16 | 17 | private AppManager() { 18 | 19 | } 20 | 21 | public static AppManager getInstance() { 22 | if (appManager == null) { 23 | synchronized (AppManager.class) { 24 | if (appManager == null) { 25 | appManager = new AppManager(); 26 | } 27 | } 28 | } 29 | return appManager; 30 | } 31 | 32 | /** 33 | * 添加一个activity 34 | * 35 | * @param activity 36 | */ 37 | public void addActivity(Activity activity) { 38 | activityStack.add(activity); 39 | } 40 | 41 | 42 | /** 43 | * 删除指定activity 44 | * 45 | * @param activity 46 | */ 47 | public void removeActivity(Activity activity) { 48 | for (int i = activityStack.size() - 1; i >= 0; i--) { 49 | if (activityStack.get(i).getClass() == activity.getClass()) { 50 | activity.finish(); 51 | activityStack.remove(activity); 52 | break; 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * 删除指定activity 59 | * 60 | * @param clazz 61 | */ 62 | public void removeActivity(Class clazz) { 63 | Activity act = null; 64 | for (int i = activityStack.size() - 1; i >= 0; i--) { 65 | act = activityStack.get(i); 66 | if (act.getClass() == clazz) { 67 | act.finish(); 68 | activityStack.remove(act); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * 删除当前activity 76 | */ 77 | public void removeCurrent() { 78 | Activity curActivity = getCurrent(); 79 | if (curActivity != null) { 80 | curActivity.finish(); 81 | activityStack.remove(curActivity); 82 | } 83 | } 84 | 85 | /** 86 | * 删除当前activity 87 | */ 88 | public Activity getCurrent() { 89 | if (!activityStack.empty()) { 90 | return activityStack.lastElement(); 91 | } 92 | return null; 93 | } 94 | 95 | /** 96 | * 清空栈 97 | */ 98 | public void clear() { 99 | while (activityStack != null && !activityStack.empty()) { 100 | Activity curActivity = getCurrent(); 101 | if (curActivity == null) { 102 | break; 103 | } 104 | curActivity.finish(); 105 | activityStack.remove(curActivity); 106 | } 107 | } 108 | 109 | /** 110 | * 求栈大小 111 | * 112 | * @return 113 | */ 114 | public int getSize() { 115 | return activityStack.size(); 116 | } 117 | 118 | /** 119 | * 保存的Activity是否为空 120 | * 在Application里判断程序是否在后台运行 121 | */ 122 | public static boolean isStackEmpty() { 123 | return activityStack == null || activityStack.size() <= 0; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/app/Constant.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.app; 2 | 3 | import com.hqumath.androidmvvm.utils.SPUtil; 4 | 5 | import java.util.HashMap; 6 | 7 | /** 8 | * **************************************************************** 9 | * 文件名称: AppNetConfig 10 | * 作 者: Created by gyd 11 | * 创建时间: 2019/1/22 14:30 12 | * 文件描述: 网络地址 13 | * 注意事项: 14 | * 版权声明: 15 | * **************************************************************** 16 | */ 17 | public class Constant { 18 | public static String baseUrl = "https://api.github.com/"; //API服务器 19 | public static String downloadHost = "http://cps.yingyonghui.com/"; //下载线路 20 | 21 | //请求通用参数 22 | public static HashMap getBaseMap() { 23 | String token = SPUtil.getInstance().getString(TOKEN); 24 | HashMap map = new HashMap<>(); 25 | map.put("token", token); 26 | return map; 27 | } 28 | 29 | //SP Key 30 | public static final String USER_NAME = "USER_NAME";//用户名 31 | public static final String TOKEN = "TOKEN"; 32 | public static final String APK_URL = "APK_URL";//apk下载地址 33 | public static final String APK_NAME = "APK_NAME";//apk文件名称 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.base; 2 | 3 | import android.app.ProgressDialog; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | /** 10 | * **************************************************************** 11 | * 文件名称: BaseActivity 12 | * 作 者: Created by gyd 13 | * 创建时间: 2019/7/2 14:56 14 | * 文件描述: 15 | * 注意事项: 16 | * 版权声明: 17 | * **************************************************************** 18 | */ 19 | public abstract class BaseActivity extends AppCompatActivity { 20 | protected BaseActivity mContext; 21 | private ProgressDialog mProgressDialog; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | mContext = this; 27 | setContentView(initContentView(savedInstanceState)); 28 | //事件监听 29 | initListener(); 30 | //初始化数据 31 | initData(); 32 | //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册 33 | initViewObservable(); 34 | } 35 | 36 | protected abstract View initContentView(Bundle savedInstanceState); 37 | 38 | protected abstract void initListener(); 39 | 40 | protected abstract void initData(); 41 | 42 | protected void initViewObservable() { 43 | } 44 | 45 | protected void showProgressDialog(String content) { 46 | if (mProgressDialog == null) { 47 | mProgressDialog = new ProgressDialog(mContext); 48 | mProgressDialog.setCancelable(true); 49 | } 50 | mProgressDialog.setMessage(content); 51 | mProgressDialog.show(); 52 | } 53 | 54 | protected void dismissProgressDialog() { 55 | if (mProgressDialog != null) { 56 | mProgressDialog.dismiss(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.base; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.fragment.app.Fragment; 13 | 14 | /** 15 | * **************************************************************** 16 | * 文件名称: BaseFragment 17 | * 作 者: Created by gyd 18 | * 创建时间: 2019/1/21 15:12 19 | * 文件描述: 20 | * 注意事项: 21 | * 版权声明: 22 | * **************************************************************** 23 | */ 24 | public abstract class BaseFragment extends Fragment { 25 | protected Activity mContext; 26 | 27 | @Override 28 | public void onAttach(@NonNull Context context) { 29 | super.onAttach(context); 30 | mContext = (Activity)context; 31 | } 32 | 33 | @Override 34 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,@Nullable Bundle savedInstanceState) { 35 | View rootView = initContentView(inflater, container, savedInstanceState); 36 | //事件监听 37 | initListener(); 38 | //初始化数据 39 | initData(); 40 | //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册 41 | initViewObservable(); 42 | return rootView; 43 | } 44 | 45 | protected abstract View initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); 46 | 47 | protected abstract void initListener(); 48 | 49 | protected abstract void initData(); 50 | 51 | protected void initViewObservable() { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/base/BaseModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.base; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.hqumath.androidmvvm.net.HandlerException; 6 | import com.hqumath.androidmvvm.net.HttpListener; 7 | import com.hqumath.androidmvvm.utils.FileUtil; 8 | 9 | import java.io.File; 10 | 11 | import io.reactivex.Observable; 12 | import io.reactivex.Observer; 13 | import io.reactivex.disposables.CompositeDisposable; 14 | import io.reactivex.disposables.Disposable; 15 | import io.reactivex.functions.Function; 16 | import io.reactivex.schedulers.Schedulers; 17 | import okhttp3.ResponseBody; 18 | 19 | /** 20 | * **************************************************************** 21 | * 文件名称: BaseModel 22 | * 作 者: Created by gyd 23 | * 创建时间: 2019/1/21 15:12 24 | * 文件描述: 防止MVP内存泄漏 25 | * 注意事项: 26 | * 版权声明: 27 | * **************************************************************** 28 | */ 29 | public class BaseModel { 30 | protected CompositeDisposable compositeDisposable = new CompositeDisposable();//管理订阅事件,用于主动取消网络请求 31 | 32 | //网络请求 33 | protected void sendRequest(Observable observable, HttpListener listener) { 34 | observable.subscribeOn(Schedulers.io()) 35 | //.observeOn(AndroidSchedulers.mainThread()) 在工作线程处理 36 | .subscribe(new Observer() { 37 | @Override 38 | public void onSubscribe(@NonNull Disposable d) { 39 | if (compositeDisposable != null) 40 | compositeDisposable.add(d); 41 | } 42 | 43 | @Override 44 | public void onNext(@NonNull Object o) { 45 | listener.onSuccess(o); 46 | } 47 | 48 | @Override 49 | public void onError(@NonNull Throwable e) { 50 | HandlerException.ResponseThrowable throwable = HandlerException.handleException(e); 51 | listener.onError(throwable.getMessage(), throwable.getCode()); 52 | } 53 | 54 | @Override 55 | public void onComplete() { 56 | } 57 | }); 58 | } 59 | 60 | //下载请求 61 | protected void sendDownloadRequest(Observable observable, HttpListener listener, File file) { 62 | observable.subscribeOn(Schedulers.io()) 63 | .map((Function) responseBody -> { 64 | FileUtil.writeFile(responseBody, file); 65 | return file; 66 | }) 67 | //.observeOn(AndroidSchedulers.mainThread()) 在工作线程处理 68 | .subscribe(new Observer() { 69 | @Override 70 | public void onSubscribe(@NonNull Disposable d) { 71 | if (compositeDisposable != null) 72 | compositeDisposable.add(d); 73 | } 74 | 75 | @Override 76 | public void onNext(@NonNull Object o) { 77 | listener.onSuccess(o); 78 | } 79 | 80 | @Override 81 | public void onError(@NonNull Throwable e) { 82 | HandlerException.ResponseThrowable throwable = HandlerException.handleException(e); 83 | listener.onError(throwable.getMessage(), throwable.getCode()); 84 | } 85 | 86 | @Override 87 | public void onComplete() { 88 | 89 | } 90 | }); 91 | } 92 | 93 | //主动解除所有订阅者 94 | protected void dispose() { 95 | if (compositeDisposable != null) { 96 | compositeDisposable.dispose(); 97 | compositeDisposable = null; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/base/BaseRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.base; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * **************************************************************** 15 | * 文件名称: BaseRecyclerAdapter 16 | * 作 者: Created by gyd 17 | * 创建时间: 2019/2/14 14:22 18 | * 文件描述: 通用RecyclerView适配器 19 | * 注意事项: 20 | * 版权声明: 21 | * **************************************************************** 22 | */ 23 | public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter { 24 | 25 | private LayoutInflater mLayoutInflater; 26 | private OnItemClickLitener mOnItemClickListener; 27 | 28 | protected List mData; 29 | private int mLayoutId; 30 | 31 | public BaseRecyclerAdapter(Context context, List mData, int layoutId) { 32 | mLayoutInflater = LayoutInflater.from(context); 33 | this.mData = mData; 34 | this.mLayoutId = layoutId; 35 | } 36 | 37 | @NonNull 38 | @Override 39 | public BaseRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 40 | View view = mLayoutInflater.inflate(mLayoutId, parent, false); 41 | if (mOnItemClickListener != null) 42 | view.setOnClickListener(v -> { 43 | int position = ((BaseRecyclerViewHolder) v.getTag()).getAdapterPosition(); 44 | //视图正在重新绘制,不要点击 45 | if (position != RecyclerView.NO_POSITION) 46 | mOnItemClickListener.onItemClick(v, position); 47 | }); 48 | return new BaseRecyclerViewHolder(view); 49 | } 50 | 51 | @Override 52 | public final void onBindViewHolder(@NonNull BaseRecyclerViewHolder holder, int position) { 53 | convert(holder, position); 54 | } 55 | 56 | /** 57 | * 该方法需要在setAdapter之前调用 58 | */ 59 | public BaseRecyclerAdapter setOnItemClickListener(OnItemClickLitener mOnItemClickListener) { 60 | this.mOnItemClickListener = mOnItemClickListener; 61 | return this; 62 | } 63 | 64 | public abstract void convert(BaseRecyclerViewHolder holder, int position); 65 | 66 | @Override 67 | public int getItemCount() { 68 | return mData.size(); 69 | } 70 | 71 | public void addItem(T item, boolean isNotify) { 72 | mData.add(item); 73 | if (isNotify) notifyDataSetChanged(); 74 | } 75 | 76 | public void addAllItem(List items, boolean isNotify) { 77 | mData.addAll(items); 78 | if (isNotify) notifyDataSetChanged(); 79 | } 80 | 81 | public void clearItems() { 82 | mData.clear(); 83 | } 84 | 85 | public interface OnItemClickLitener { 86 | void onItemClick(View view, int position); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/base/BaseRecyclerViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.base; 2 | 3 | import android.util.SparseArray; 4 | import android.view.View; 5 | import android.widget.TextView; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | /** 11 | * **************************************************************** 12 | * 文件名称: BaseRecyclerViewHolder 13 | * 作 者: Created by gyd 14 | * 创建时间: 2019/2/14 14:27 15 | * 文件描述: 封装了基础的ViewHolder使用方法 16 | * 注意事项: 17 | * 版权声明: 18 | * **************************************************************** 19 | */ 20 | public class BaseRecyclerViewHolder extends RecyclerView.ViewHolder { 21 | 22 | private View mConvertView; 23 | private SparseArray mViews = new SparseArray(); 24 | 25 | public BaseRecyclerViewHolder(@NonNull View itemView) { 26 | super(itemView); 27 | mConvertView = itemView; 28 | mConvertView.setTag(this); 29 | } 30 | 31 | public T getView(int viewId) { 32 | View view = mViews.get(viewId); 33 | if (view == null) { 34 | view = mConvertView.findViewById(viewId); 35 | mViews.put(viewId, view); 36 | } 37 | return (T) view; 38 | } 39 | 40 | public BaseRecyclerViewHolder setText(int viewId, String text) { 41 | TextView view = getView(viewId); 42 | view.setText(text); 43 | return this; 44 | } 45 | 46 | public View getHolderView() { 47 | return mConvertView; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/base/BaseViewModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.base; 2 | 3 | import androidx.lifecycle.ViewModel; 4 | 5 | import com.hqumath.androidmvvm.repository.MyModel; 6 | 7 | public class BaseViewModel extends ViewModel { 8 | protected BaseModel mModel; 9 | 10 | /** 11 | * 解除model中所有订阅者 12 | */ 13 | public void dispose() { 14 | if (mModel != null) { 15 | mModel.dispose(); 16 | mModel = null; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/bean/BaseResultEntity.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.bean; 2 | 3 | /** 4 | * 网络请求统一返回格式 5 | */ 6 | public class BaseResultEntity { 7 | //判断标示 8 | private String type; 9 | //提示信息 10 | private String resultMsg; 11 | //错误码 12 | private String resultCode; 13 | //显示数据(用户需要关心的数据) 14 | private T data; 15 | 16 | public String getType() { 17 | return type; 18 | } 19 | 20 | public void setType(String type) { 21 | this.type = type; 22 | } 23 | 24 | public String getResultMsg() { 25 | return resultMsg; 26 | } 27 | 28 | public void setResultMsg(String resultMsg) { 29 | this.resultMsg = resultMsg; 30 | } 31 | 32 | public String getResultCode() { 33 | return resultCode; 34 | } 35 | 36 | public void setResultCode(String resultCode) { 37 | this.resultCode = resultCode; 38 | } 39 | 40 | public T getData() { 41 | return data; 42 | } 43 | 44 | public void setData(T data) { 45 | this.data = data; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/ApiService.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net; 2 | 3 | 4 | import com.hqumath.androidmvvm.bean.BaseResultEntity; 5 | import com.hqumath.androidmvvm.bean.CommitEntity; 6 | import com.hqumath.androidmvvm.bean.ReposEntity; 7 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 8 | 9 | import java.util.List; 10 | 11 | import io.reactivex.Observable; 12 | import okhttp3.MultipartBody; 13 | import okhttp3.ResponseBody; 14 | import retrofit2.http.GET; 15 | import retrofit2.http.Multipart; 16 | import retrofit2.http.POST; 17 | import retrofit2.http.Part; 18 | import retrofit2.http.Path; 19 | import retrofit2.http.Query; 20 | import retrofit2.http.Streaming; 21 | import retrofit2.http.Url; 22 | 23 | /** 24 | * **************************************************************** 25 | * 文件名称: MainService 26 | * 作 者: Created by gyd 27 | * 创建时间: 2019/1/22 17:11 28 | * 文件描述: 29 | * 注意事项: 30 | * 版权声明: 31 | * **************************************************************** 32 | */ 33 | public interface ApiService { 34 | 35 | //获取用户信息 36 | @GET("users/{userName}") 37 | Observable getUserInfo(@Path("userName") String userName); 38 | 39 | //获取用户仓库 40 | @GET("users/{userName}/repos") 41 | Observable> getMyRepos(@Path("userName") String userName, @Query("per_page") int per_page, @Query("page") long page); 42 | 43 | //获取星标仓库 44 | @GET("users/{userName}/starred") 45 | Observable> getStarred(@Path("userName") String userName, @Query("per_page") int per_page, @Query("page") long page); 46 | 47 | //获取被追随 48 | @GET("users/{userName}/followers") 49 | Observable> getFollowers(@Path("userName") String userName, @Query("per_page") int per_page, @Query("page") long page); 50 | 51 | //获取仓库信息 52 | @GET("repos/{userName}/{reposName}") 53 | Observable getReposInfo(@Path("userName") String userName, 54 | @Path("reposName") String reposName); 55 | 56 | //获取仓库提交记录 分页 57 | @GET("repos/{userName}/{reposName}/commits?sha=master") 58 | Observable> getCommits(@Path("userName") String userName, 59 | @Path("reposName") String reposName, 60 | @Query("per_page") int per_page, @Query("page") long page); 61 | 62 | //文件下载 63 | @Streaming/*大文件需要加入这个判断,防止下载过程中写入到内存中*/ 64 | @GET 65 | Observable download(@Url String url); 66 | 67 | //文件上传 模拟 68 | @Multipart 69 | @POST("api/user/update") 70 | Observable upload(@Part MultipartBody.Part part); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/CreateRequestBodyUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.io.File; 6 | import java.util.HashMap; 7 | 8 | import okhttp3.MultipartBody; 9 | import okhttp3.RequestBody; 10 | 11 | /** 12 | * 生成请求体 13 | */ 14 | public class CreateRequestBodyUtil { 15 | /** 16 | * 根据Json串生成请求体 17 | * 18 | * @param json 19 | * @return 20 | */ 21 | public static RequestBody createRequestBody(String json) { 22 | return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), json); 23 | } 24 | 25 | /** 26 | * 根据Json串生成请求体 27 | * 28 | * @return 29 | */ 30 | public static RequestBody createRequestBody(HashMap map) { 31 | Gson gson = new Gson(); 32 | String body = gson.toJson(map); 33 | return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), body); 34 | } 35 | 36 | /** 37 | * 根据Json串生成请求体 38 | * 39 | * @return 40 | */ 41 | public static RequestBody createRequestBody(Object bean) { 42 | Gson gson = new Gson(); 43 | String body = gson.toJson(bean); 44 | return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), body); 45 | } 46 | 47 | /** 48 | * 根据File生成请求体 49 | * 50 | * @return 51 | */ 52 | public static MultipartBody.Part createRequestBody(String key, File file) { 53 | RequestBody requestFile = RequestBody.create(okhttp3.MediaType.parse("multipart/form-data"), file); 54 | MultipartBody.Part body = MultipartBody.Part.createFormData(key, key, requestFile);//(key, file.getName(), requestFile) 55 | return body; 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/HttpListener.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net; 2 | 3 | public interface HttpListener { 4 | void onSuccess(Object object); 5 | 6 | void onError(String errorMsg, String code); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/LogInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net; 2 | 3 | 4 | import com.hqumath.androidmvvm.utils.LogUtil; 5 | 6 | import java.io.IOException; 7 | 8 | import okhttp3.Interceptor; 9 | import okhttp3.Request; 10 | import okhttp3.Response; 11 | import okhttp3.ResponseBody; 12 | 13 | /** 14 | * 自定义拦截器 15 | * 打印日志 16 | */ 17 | public class LogInterceptor implements Interceptor { 18 | 19 | @Override 20 | public Response intercept(Chain chain) throws IOException { 21 | Request request = chain.request(); 22 | Response response;//打印请求异常信息 23 | try { 24 | response = chain.proceed(request.newBuilder().build()); 25 | } catch (Exception e) { 26 | LogUtil.d("HTTP", "网络请求异常\n" + e); 27 | throw e; 28 | } 29 | //response.body()调用后,response中的流会被关闭 30 | ResponseBody responseBody = response.peekBody(1024 * 1024); 31 | //解析出参 32 | String content = responseBody.string(); 33 | //JSONObject jsonObject = new JSONObject(content); //统一处理响应数据 34 | 35 | //打印出参 36 | LogUtil.d("HTTP", "=====\n" + response.request().method() + ": " 37 | + response.request().url() + "\n" + content); 38 | return response; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/RetrofitClient.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net; 2 | 3 | import com.hqumath.androidmvvm.app.Constant; 4 | import com.hqumath.androidmvvm.net.download.DownloadInterceptor; 5 | import com.hqumath.androidmvvm.net.download.DownloadListener; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import okhttp3.OkHttpClient; 10 | import retrofit2.Retrofit; 11 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 12 | import retrofit2.converter.gson.GsonConverterFactory; 13 | 14 | /** 15 | * **************************************************************** 16 | * 文件名称: RetrofitClient 17 | * 作 者: Created by gyd 18 | * 创建时间: 2019/1/22 14:47 19 | * 文件描述: RetrofitClient封装单例类, 实现网络请求 20 | * 注意事项: https://github.com/wzgiceman/RxjavaRetrofitDemo-master 21 | * 每次发送请求,new Retrofit,以便动态更改baseUrl 22 | * 版权声明: 23 | * **************************************************************** 24 | */ 25 | public class RetrofitClient { 26 | private volatile static RetrofitClient INSTANCE; 27 | private final static int connectTimeout = 6;//s,连接超时 28 | private final static int readTimeout = 6;//s,读取超时 29 | private final static int writeTimeout = 6;//s,写超时 30 | 31 | private ApiService apiService;//api服务器 32 | private ApiService downloadService;//下载服务器 33 | 34 | //获取单例 35 | public static RetrofitClient getInstance() { 36 | if (INSTANCE == null) { 37 | synchronized (RetrofitClient.class) { 38 | if (INSTANCE == null) { 39 | INSTANCE = new RetrofitClient(); 40 | } 41 | } 42 | } 43 | return INSTANCE; 44 | } 45 | 46 | //构造方法私有 47 | private RetrofitClient() { 48 | } 49 | 50 | //api服务器 51 | public ApiService getApiService() { 52 | if (apiService == null) { 53 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 54 | builder.connectTimeout(connectTimeout, TimeUnit.SECONDS); 55 | builder.readTimeout(readTimeout, TimeUnit.SECONDS); 56 | builder.writeTimeout(writeTimeout, TimeUnit.SECONDS); 57 | builder.retryOnConnectionFailure(false);//出现错误时会重新发送请求 58 | builder.addInterceptor(new LogInterceptor());//自定义拦截器(token过期后刷新token,打印日志) 59 | Retrofit retrofit = new Retrofit.Builder() 60 | .client(builder.build()) 61 | .addConverterFactory(GsonConverterFactory.create()) 62 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 63 | .baseUrl(Constant.baseUrl) 64 | .build(); 65 | apiService = retrofit.create(ApiService.class); 66 | } 67 | return apiService; 68 | } 69 | 70 | //下载服务器 71 | public ApiService getDownloadService(DownloadListener listener) { 72 | if (downloadService == null) { 73 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 74 | builder.connectTimeout(connectTimeout, TimeUnit.SECONDS); 75 | builder.readTimeout(readTimeout, TimeUnit.SECONDS); 76 | builder.writeTimeout(writeTimeout, TimeUnit.SECONDS); 77 | builder.retryOnConnectionFailure(false);//出现错误时会重新发送请求 78 | if (listener != null) 79 | builder.addInterceptor(new DownloadInterceptor(listener));//下载拦截器(显示进度) 80 | Retrofit retrofit = new Retrofit.Builder() 81 | .client(builder.build()) 82 | .addConverterFactory(GsonConverterFactory.create()) 83 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 84 | .baseUrl(Constant.downloadHost) 85 | .build(); 86 | downloadService = retrofit.create(ApiService.class); 87 | } 88 | return downloadService; 89 | } 90 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/TokenException.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net; 2 | 3 | public class TokenException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/download/DownloadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net.download; 2 | 3 | import java.io.IOException; 4 | 5 | import okhttp3.Interceptor; 6 | import okhttp3.Response; 7 | 8 | /** 9 | * 下载拦截器,显示进度 10 | */ 11 | public class DownloadInterceptor implements Interceptor { 12 | 13 | private DownloadListener listener; 14 | 15 | public DownloadInterceptor(DownloadListener listener) { 16 | this.listener = listener; 17 | } 18 | 19 | @Override 20 | public Response intercept(Chain chain) throws IOException { 21 | Response originalResponse = chain.proceed(chain.request()); 22 | 23 | return originalResponse.newBuilder() 24 | .body(new DownloadResponseBody(originalResponse.body(), listener)) 25 | .build(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/download/DownloadListener.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net.download; 2 | 3 | public interface DownloadListener { 4 | void onSuccess(Object object); 5 | 6 | void onError(String errorMsg, String code); 7 | 8 | void update(long read, long count);//已下载,总量 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/download/DownloadResponseBody.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net.download; 2 | 3 | import java.io.IOException; 4 | 5 | import okhttp3.MediaType; 6 | import okhttp3.ResponseBody; 7 | import okio.Buffer; 8 | import okio.BufferedSource; 9 | import okio.ForwardingSource; 10 | import okio.Okio; 11 | import okio.Source; 12 | 13 | /** 14 | * 自定义精度的body 15 | * 16 | * @author wzg 17 | */ 18 | public class DownloadResponseBody extends ResponseBody { 19 | 20 | private ResponseBody responseBody; 21 | private DownloadListener progressListener; 22 | private BufferedSource bufferedSource; 23 | 24 | public DownloadResponseBody(ResponseBody responseBody, DownloadListener progressListener) { 25 | this.responseBody = responseBody; 26 | this.progressListener = progressListener; 27 | } 28 | 29 | @Override 30 | public MediaType contentType() { 31 | return responseBody.contentType(); 32 | } 33 | 34 | @Override 35 | public long contentLength() { 36 | return responseBody.contentLength(); 37 | } 38 | 39 | @Override 40 | public BufferedSource source() { 41 | if (bufferedSource == null) { 42 | bufferedSource = Okio.buffer(source(responseBody.source())); 43 | } 44 | return bufferedSource; 45 | } 46 | 47 | private Source source(Source source) { 48 | return new ForwardingSource(source) { 49 | long totalBytesRead = 0L; 50 | 51 | @Override 52 | public long read(Buffer sink, long byteCount) throws IOException { 53 | long bytesRead = super.read(sink, byteCount); 54 | // read() returns the number of bytes read, or -1 if this source is exhausted. 55 | totalBytesRead += bytesRead != -1 ? bytesRead : 0; 56 | if (null != progressListener) { 57 | progressListener.update(totalBytesRead, responseBody.contentLength()); 58 | } 59 | return bytesRead; 60 | } 61 | }; 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/upload/ProgressRequestBody.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net.upload; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.io.IOException; 6 | 7 | import okhttp3.MediaType; 8 | import okhttp3.RequestBody; 9 | import okio.Buffer; 10 | import okio.BufferedSink; 11 | import okio.ForwardingSink; 12 | import okio.Okio; 13 | import okio.Sink; 14 | 15 | /** 16 | * 自定义回调加载速度类RequestBody 17 | * Created by WZG on 2016/10/20. 18 | */ 19 | 20 | public class ProgressRequestBody extends RequestBody { 21 | //实际起作用的RequestBody 22 | private RequestBody delegate; 23 | //进度回调接口 24 | private final UploadProgressListener progressListener; 25 | 26 | public ProgressRequestBody(RequestBody requestBody, UploadProgressListener progressListener) { 27 | this.delegate = requestBody; 28 | this.progressListener = progressListener; 29 | } 30 | 31 | @Override 32 | public MediaType contentType() { 33 | return delegate.contentType(); 34 | } 35 | 36 | @Override 37 | public void writeTo(@NonNull BufferedSink sink) throws IOException { 38 | //不需要检测日志拦截器进度 HttpLoggingInterceptor 39 | if (sink instanceof Buffer) { 40 | delegate.writeTo(sink); 41 | return; 42 | } 43 | //将CountingSink转化为BufferedSink供writeTo()使用 44 | BufferedSink bufferedSink = Okio.buffer(new CountingSink(sink)); 45 | delegate.writeTo(bufferedSink); 46 | bufferedSink.flush(); 47 | } 48 | 49 | private class CountingSink extends ForwardingSink { 50 | private long byteWritten; 51 | 52 | private CountingSink(Sink delegate) { 53 | super(delegate); 54 | } 55 | 56 | /** 57 | * 上传时调用该方法,在其中调用回调函数将上传进度暴露出去,该方法提供了缓冲区的自己大小 58 | */ 59 | @Override 60 | public void write(@NonNull Buffer source, long byteCount) throws IOException { 61 | super.write(source, byteCount); 62 | byteWritten += byteCount; 63 | progressListener.onProgress(byteWritten, contentLength()); 64 | } 65 | } 66 | 67 | /** 68 | * 返回文件总的字节大小 69 | * 如果文件大小获取失败则返回-1 70 | */ 71 | @Override 72 | public long contentLength() { 73 | try { 74 | return delegate.contentLength(); 75 | } catch (IOException e) { 76 | return -1; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/net/upload/UploadProgressListener.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.net.upload; 2 | 3 | /** 4 | * 上传进度回调类 5 | * Created by WZG on 2016/10/20. 6 | */ 7 | 8 | public interface UploadProgressListener { 9 | /** 10 | * 上传进度 11 | */ 12 | void onProgress(long currentBytesCount, long totalBytesCount); 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/repository/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.repository; 2 | 3 | import androidx.room.Database; 4 | import androidx.room.Room; 5 | import androidx.room.RoomDatabase; 6 | 7 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 8 | import com.hqumath.androidmvvm.repository.dao.UserInfoDao; 9 | import com.hqumath.androidmvvm.utils.CommonUtil; 10 | 11 | /** 12 | * **************************************************************** 13 | * 文件名称: AppDatabase 14 | * 作 者: Created by gyd 15 | * 创建时间: 2019/7/4 11:35 16 | * 文件描述: 数据库 17 | * 注意事项: onDestroy时关闭数据库 18 | * 版权声明: 19 | * **************************************************************** 20 | */ 21 | @Database(entities = {UserInfoEntity.class}, version = 2, exportSchema = false) 22 | public abstract class AppDatabase extends RoomDatabase { 23 | private static class AppDataBaseHolder { 24 | private static final AppDatabase instance = 25 | Room.databaseBuilder(CommonUtil.getContext(), AppDatabase.class, "basic.db") 26 | .fallbackToDestructiveMigration()//升级时丢弃原来表 27 | .build(); 28 | } 29 | 30 | public static AppDatabase getInstance() { 31 | return AppDataBaseHolder.instance; 32 | } 33 | 34 | public abstract UserInfoDao userInfoDao(); 35 | 36 | //注意,onDestroy时关闭数据库 close(); 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/repository/dao/UserInfoDao.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.repository.dao; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.room.Dao; 5 | import androidx.room.Insert; 6 | import androidx.room.OnConflictStrategy; 7 | import androidx.room.Query; 8 | 9 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * **************************************************************** 15 | * 文件名称: UserInfoDao 16 | * 作 者: Created by gyd 17 | * 创建时间: 2019/7/4 11:35 18 | * 文件描述: 用户信息表操作 19 | * 注意事项: 20 | * 版权声明: 21 | * **************************************************************** 22 | */ 23 | @Dao 24 | public interface UserInfoDao { 25 | @Query("select * from user_info") 26 | LiveData> loadAll(); 27 | 28 | @Query("select * from user_info") 29 | List loadAll1(); 30 | 31 | // @Query("SELECT * FROM user_info ORDER BY indexInResponse ASC") 32 | // DataSource.Factory loadAll2(); PagingSource 33 | 34 | @Insert(onConflict = OnConflictStrategy.REPLACE) 35 | void insertAll(List entity); 36 | 37 | // @Update(onConflict = OnConflictStrategy.REPLACE) 38 | // void updateAll(List entity); 39 | // 40 | // @Delete 41 | // void deleteAll(List entity); 42 | 43 | @Query("delete from user_info") 44 | void deleteAll(); 45 | 46 | @Query("SELECT MAX(indexInResponse) + 1 FROM user_info") 47 | int getNextIndex(); 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/service/UpdateService.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.service; 2 | 3 | import android.app.DownloadManager; 4 | import android.app.Service; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.net.Uri; 10 | import android.os.Build; 11 | import android.os.Environment; 12 | import android.os.IBinder; 13 | import android.text.TextUtils; 14 | 15 | import com.hqumath.androidmvvm.app.Constant; 16 | import com.hqumath.androidmvvm.utils.SPUtil; 17 | 18 | import java.io.File; 19 | 20 | /** 21 | * 版本更新 服务 22 | */ 23 | public class UpdateService extends Service { 24 | private boolean isDown = false; 25 | private String apkName = ""; 26 | 27 | //安卓系统下载类 28 | DownloadManager manager; 29 | //接收下载完的广播 30 | DownloadCompleteReceiver receiver; 31 | 32 | public UpdateService() { 33 | } 34 | 35 | //初始化下载器 36 | private void initDownManager() { 37 | if (!isDown) { 38 | isDown = true; 39 | manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); 40 | receiver = new DownloadCompleteReceiver(); 41 | 42 | String apkUrl = SPUtil.getInstance().getString(Constant.APK_URL); 43 | String Name = SPUtil.getInstance().getString(Constant.APK_NAME); 44 | if (TextUtils.isEmpty(Name)) 45 | apkName = getPackageName() + ".apk"; 46 | else 47 | apkName = Name + ".apk"; 48 | 49 | Uri parse = Uri.parse(apkUrl); 50 | DownloadManager.Request down = new DownloadManager.Request(parse); 51 | // 设置允许使用的网络类型,这里是移动网络和wifi都可以 52 | down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); 53 | // 下载时,通知栏显示途中 54 | down.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); 55 | // 显示下载界面 56 | down.setVisibleInDownloadsUi(true); 57 | // 设置下载后文件存放的位置 58 | down.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, apkName); 59 | // 将下载请求放入队列 60 | manager.enqueue(down); 61 | //注册下载广播 62 | registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); 63 | } 64 | } 65 | 66 | @Override 67 | public int onStartCommand(Intent intent, int flags, int startId) { 68 | // 调用下载 69 | initDownManager(); 70 | return super.onStartCommand(intent, flags, startId); 71 | } 72 | 73 | @Override 74 | public IBinder onBind(Intent intent) { 75 | return null; 76 | } 77 | 78 | @Override 79 | public void onDestroy() { 80 | // 注销下载广播 81 | if (receiver != null) 82 | unregisterReceiver(receiver); 83 | super.onDestroy(); 84 | } 85 | 86 | // 接受下载完成后的intent 87 | class DownloadCompleteReceiver extends BroadcastReceiver { 88 | 89 | @Override 90 | public void onReceive(Context context, Intent intent) { 91 | //判断是否下载完成的广播 92 | if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { 93 | //获取下载的文件id 94 | long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); 95 | //自动安装apk 96 | Uri uriForDownloadedFile = manager.getUriForDownloadedFile(downId); 97 | installApkNew(uriForDownloadedFile); 98 | //停止服务并关闭广播 99 | UpdateService.this.stopSelf(); 100 | } 101 | } 102 | 103 | //安装apk 104 | protected void installApkNew(Uri uri) { 105 | if (Build.VERSION.SDK_INT >= 24) {//判读版本是否在7.0以上 106 | Intent install = new Intent(Intent.ACTION_VIEW); 107 | install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 108 | install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//添加这一句表示对目标应用临时授权该Uri所代表的文件 109 | install.setDataAndType(uri, "application/vnd.android.package-archive"); 110 | getApplicationContext().startActivity(install); 111 | } else { 112 | //魅族手机URI不正确getExternalFilesDir 113 | if (!uri.toString().contains("file://")) { 114 | File file = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS + File.separator + apkName); 115 | if (file != null) { 116 | String path = file.getAbsolutePath(); 117 | uri = Uri.parse("file://" + path); 118 | } 119 | } 120 | Intent intent = new Intent(Intent.ACTION_VIEW); 121 | intent.setDataAndType(uri, "application/vnd.android.package-archive"); 122 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 123 | getApplicationContext().startActivity(intent); 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/fileupdown/FileUpDownActivity.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.fileupdown; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import androidx.lifecycle.ViewModelProvider; 7 | 8 | import com.hqumath.androidmvvm.base.BaseActivity; 9 | import com.hqumath.androidmvvm.databinding.ActivityFileupdownBinding; 10 | import com.hqumath.androidmvvm.utils.CommonUtil; 11 | import com.hqumath.androidmvvm.utils.FileUtil; 12 | import com.hqumath.androidmvvm.widget.DownloadingDialog; 13 | 14 | import java.io.File; 15 | 16 | public class FileUpDownActivity extends BaseActivity { 17 | public final static String url = "http://cps.yingyonghui.com/cps/yyh/channel/ac.union.m2/com.yingyonghui.market_1_30063293.apk"; 18 | 19 | private ActivityFileupdownBinding binding; 20 | private FileUpDownViewModel viewModel; 21 | private DownloadingDialog mDownloadingDialog; 22 | 23 | @Override 24 | public View initContentView(Bundle savedInstanceState) { 25 | binding = ActivityFileupdownBinding.inflate(getLayoutInflater()); 26 | return binding.getRoot(); 27 | } 28 | 29 | @Override 30 | protected void initListener() { 31 | binding.btnDownload.setOnClickListener(v -> { 32 | viewModel.download(url); 33 | }); 34 | binding.btnUpload.setOnClickListener(v -> { 35 | File file = FileUtil.getFileFromUrl(url); 36 | if(file.exists()) { 37 | viewModel.upload("testFile", file); 38 | } else { 39 | CommonUtil.toast("文件不存在,请先下载"); 40 | } 41 | }); 42 | } 43 | 44 | @Override 45 | protected void initData() { 46 | viewModel = new ViewModelProvider(this).get(FileUpDownViewModel.class); 47 | } 48 | 49 | @Override 50 | protected void initViewObservable() { 51 | viewModel.isDownloading.observe(this, b -> { 52 | if (b) { 53 | if (mDownloadingDialog == null) { 54 | mDownloadingDialog = new DownloadingDialog(this); 55 | } 56 | mDownloadingDialog.show(); 57 | } else { 58 | if (mDownloadingDialog != null) { 59 | mDownloadingDialog.dismiss(); 60 | } 61 | } 62 | }); 63 | viewModel.downloadProgress.observe(this, progress -> { 64 | if (mDownloadingDialog != null && mDownloadingDialog.isShowing()) { 65 | mDownloadingDialog.setProgress(progress, viewModel.downloadMax); 66 | } 67 | }); 68 | viewModel.downloadResultCode.observe(this, code -> { 69 | if (code.equals("0")) { 70 | String fileName = (viewModel.downloadResultData).getName(); 71 | CommonUtil.toast(fileName + "Download success."); 72 | } else { 73 | CommonUtil.toast(viewModel.downloadResultMsg); 74 | } 75 | }); 76 | viewModel.isLoading.observe(this, b -> { 77 | if (b) { 78 | showProgressDialog("loading"); 79 | } else { 80 | dismissProgressDialog(); 81 | } 82 | }); 83 | viewModel.uploadResultCode.observe(this, code -> { 84 | if (code.equals("0")) { 85 | CommonUtil.toast("Upload success."); 86 | } else { 87 | CommonUtil.toast(viewModel.uploadResultMsg); 88 | } 89 | }); 90 | } 91 | 92 | @Override 93 | protected void onDestroy() { 94 | if (viewModel != null) { 95 | viewModel.dispose(); 96 | viewModel = null; 97 | } 98 | super.onDestroy(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/fileupdown/FileUpDownViewModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.fileupdown; 2 | 3 | import androidx.lifecycle.MutableLiveData; 4 | 5 | import com.hqumath.androidmvvm.base.BaseViewModel; 6 | import com.hqumath.androidmvvm.net.HttpListener; 7 | import com.hqumath.androidmvvm.net.download.DownloadListener; 8 | import com.hqumath.androidmvvm.repository.MyModel; 9 | import com.hqumath.androidmvvm.utils.FileUtil; 10 | 11 | import java.io.File; 12 | 13 | public class FileUpDownViewModel extends BaseViewModel { 14 | 15 | //下载 16 | public MutableLiveData isDownloading = new MutableLiveData<>(); 17 | public MutableLiveData downloadProgress = new MutableLiveData<>(); 18 | public long downloadMax;//总量 19 | public MutableLiveData downloadResultCode = new MutableLiveData<>();//0成功;other失败 20 | public String downloadResultMsg; 21 | public File downloadResultData; 22 | 23 | //上传 24 | public MutableLiveData isLoading = new MutableLiveData<>(); 25 | public MutableLiveData uploadResultCode = new MutableLiveData<>();//0成功;other失败 26 | public String uploadResultMsg; 27 | public Object uploadResultData; 28 | 29 | public FileUpDownViewModel() { 30 | mModel = new MyModel(); 31 | } 32 | 33 | public void download(String url) { 34 | isDownloading.setValue(true); 35 | File file = FileUtil.getFileFromUrl(url); 36 | ((MyModel) mModel).download(url, file, new DownloadListener() { 37 | @Override 38 | public void onSuccess(Object object) { 39 | isDownloading.postValue(false); 40 | downloadResultData = (File) object; 41 | downloadResultCode.postValue("0"); 42 | } 43 | 44 | @Override 45 | public void onError(String errorMsg, String code) { 46 | isDownloading.postValue(false); 47 | downloadResultMsg = errorMsg; 48 | downloadResultCode.postValue(code); 49 | } 50 | 51 | @Override 52 | public void update(long read, long count) { 53 | downloadMax = count; 54 | downloadProgress.postValue(read); 55 | } 56 | }); 57 | } 58 | 59 | public void upload(String key, File file) { 60 | isLoading.setValue(true); 61 | ((MyModel) mModel).upload(key, file, new HttpListener() { 62 | @Override 63 | public void onSuccess(Object object) { 64 | isLoading.postValue(false); 65 | uploadResultData = object; 66 | uploadResultCode.postValue("0"); 67 | } 68 | 69 | @Override 70 | public void onError(String errorMsg, String code) { 71 | isLoading.postValue(false); 72 | uploadResultMsg = errorMsg; 73 | uploadResultCode.postValue(code); 74 | } 75 | }); 76 | } 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/follow/FollowersFragment.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.follow; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.lifecycle.ViewModelProvider; 9 | 10 | import com.hqumath.androidmvvm.adapter.MyRecyclerAdapters; 11 | import com.hqumath.androidmvvm.base.BaseFragment; 12 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 13 | import com.hqumath.androidmvvm.databinding.FragmentFollowersBinding; 14 | import com.hqumath.androidmvvm.utils.CommonUtil; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * **************************************************************** 21 | * 文件名称: FollowersFragment 22 | * 作 者: Created by gyd 23 | * 创建时间: 2019/11/5 10:06 24 | * 文件描述: 使用 Room 持久化存储列表数据 25 | * 注意事项: 26 | * 版权声明: 27 | * **************************************************************** 28 | */ 29 | public class FollowersFragment extends BaseFragment { 30 | 31 | private FragmentFollowersBinding binding; 32 | private FollowersViewModel viewModel; 33 | private MyRecyclerAdapters.FollowRecyclerAdapter recyclerAdapter; 34 | protected boolean hasRequested;//在onResume中判断是否已经请求过数据。用于懒加载 35 | 36 | @Override 37 | public View initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 38 | binding = FragmentFollowersBinding.inflate(inflater, container, false); 39 | return binding.getRoot(); 40 | } 41 | 42 | @Override 43 | protected void initListener() { 44 | binding.refreshLayout.setOnRefreshListener(v -> viewModel.getFollowers(true)); 45 | binding.refreshLayout.setOnLoadMoreListener(v -> viewModel.getFollowers(false)); 46 | } 47 | 48 | @Override 49 | protected void initData() { 50 | viewModel = new ViewModelProvider(requireActivity()).get(FollowersViewModel.class); 51 | 52 | recyclerAdapter = new MyRecyclerAdapters.FollowRecyclerAdapter(mContext, new ArrayList<>()); 53 | recyclerAdapter.setOnItemClickListener((v, position) -> { 54 | List list = viewModel.mData.getValue(); 55 | if (list != null && list.size() > position) { 56 | UserInfoEntity data = list.get(position); 57 | startActivity(ProfileDetailActivity.getStartIntent(mContext, data.getLogin())); 58 | } 59 | }); 60 | binding.recyclerView.setAdapter(recyclerAdapter); 61 | } 62 | 63 | @Override 64 | protected void initViewObservable() { 65 | //根据请求调整UI 66 | viewModel.followersResultCode.observe(this, code -> { 67 | if (code.equals("0")) { 68 | if (viewModel.followersRefresh) { 69 | if (viewModel.followersNewEmpty) { 70 | binding.refreshLayout.finishRefreshWithNoMoreData();//上拉加载功能将显示没有更多数据 71 | } else { 72 | binding.refreshLayout.finishRefresh(); 73 | } 74 | } else { 75 | if (viewModel.followersNewEmpty) { 76 | binding.refreshLayout.finishLoadMoreWithNoMoreData();//上拉加载功能将显示没有更多数据 77 | } else { 78 | binding.refreshLayout.finishLoadMore(); 79 | } 80 | } 81 | } else { 82 | CommonUtil.toast(viewModel.followersResultMsg); 83 | if (viewModel.followersRefresh) { 84 | binding.refreshLayout.finishRefresh(false);//刷新失败,会影响到上次的更新时间 85 | } else { 86 | binding.refreshLayout.finishLoadMore(false); 87 | } 88 | } 89 | }); 90 | //根据数据库刷新列表 91 | viewModel.mData.observe(this, list -> { 92 | recyclerAdapter.setData(list); 93 | recyclerAdapter.notifyDataSetChanged(); 94 | binding.emptyLayout.llEmpty.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE); 95 | }); 96 | } 97 | 98 | @Override 99 | public void onResume() { 100 | super.onResume(); 101 | if (!hasRequested) { 102 | hasRequested = true; 103 | binding.refreshLayout.autoRefresh();//触发自动刷新 104 | } 105 | } 106 | 107 | @Override 108 | public void onDestroy() { 109 | if (viewModel != null) { 110 | viewModel.dispose(); 111 | viewModel = null; 112 | } 113 | super.onDestroy(); 114 | } 115 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/follow/FollowersViewModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.follow; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.lifecycle.MutableLiveData; 5 | 6 | import com.hqumath.androidmvvm.app.Constant; 7 | import com.hqumath.androidmvvm.base.BaseViewModel; 8 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 9 | import com.hqumath.androidmvvm.net.HttpListener; 10 | import com.hqumath.androidmvvm.repository.AppDatabase; 11 | import com.hqumath.androidmvvm.repository.MyModel; 12 | import com.hqumath.androidmvvm.utils.SPUtil; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * **************************************************************** 18 | * 文件名称: LoginViewModel 19 | * 作 者: Created by gyd 20 | * 创建时间: 2019/6/3 10:27 21 | * 文件描述: 使用 Room 持久化存储列表数据,Network => DB => LiveData => RecyclerView 22 | * 注意事项: 自动加载数据库中全部数据,不能逐渐加载 23 | * 版权声明: 24 | * **************************************************************** 25 | */ 26 | public class FollowersViewModel extends BaseViewModel { 27 | 28 | private final int pageSize = 10;//分页 29 | private long pageIndex;//索引 30 | 31 | public MutableLiveData followersResultCode = new MutableLiveData<>();//列表请求 0成功;other失败 32 | public String followersResultMsg; 33 | public boolean followersRefresh;//true 下拉刷新;false 上拉加载 34 | public boolean followersNewEmpty;//true 增量为空;false 增量不为空 35 | public LiveData> mData;//列表数据 36 | 37 | public FollowersViewModel() { 38 | mModel = new MyModel(); 39 | mData = AppDatabase.getInstance().userInfoDao().loadAll();//UserInfoDao_Impl 内部做了线程切换 40 | } 41 | 42 | @Override 43 | public void dispose() { 44 | super.dispose(); 45 | AppDatabase.getInstance().close();//关闭数据库 46 | } 47 | 48 | /** 49 | * 获取列表 50 | * 51 | * @param isRefresh true 下拉刷新;false 上拉加载 52 | */ 53 | public void getFollowers(boolean isRefresh) { 54 | if (isRefresh) { 55 | pageIndex = 1; 56 | } 57 | String userName = SPUtil.getInstance().getString(Constant.USER_NAME); 58 | ((MyModel) mModel).getFollowers(userName, pageSize, pageIndex, new HttpListener() { 59 | @Override 60 | public void onSuccess(Object object) { 61 | List list = (List) object; 62 | pageIndex++;//偏移量+1 63 | if (isRefresh) {//下拉覆盖,上拉增量 64 | AppDatabase.getInstance().userInfoDao().deleteAll(); 65 | } 66 | if (!list.isEmpty()) { 67 | AppDatabase.getInstance().userInfoDao().insertAll(list); 68 | } 69 | followersRefresh = isRefresh; 70 | followersNewEmpty = list.isEmpty(); 71 | followersResultCode.postValue("0"); 72 | } 73 | 74 | @Override 75 | public void onError(String errorMsg, String code) { 76 | followersResultMsg = errorMsg; 77 | followersRefresh = isRefresh; 78 | followersResultCode.postValue(code); 79 | } 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/follow/FollowersViewModelOld.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.follow; 2 | 3 | import androidx.lifecycle.MutableLiveData; 4 | 5 | import com.hqumath.androidmvvm.app.Constant; 6 | import com.hqumath.androidmvvm.base.BaseViewModel; 7 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 8 | import com.hqumath.androidmvvm.net.HttpListener; 9 | import com.hqumath.androidmvvm.repository.MyModel; 10 | import com.hqumath.androidmvvm.utils.SPUtil; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * **************************************************************** 17 | * 文件名称: LoginViewModel 18 | * 作 者: Created by gyd 19 | * 创建时间: 2019/6/3 10:27 20 | * 文件描述: 21 | * 注意事项: 22 | * 版权声明: 23 | * **************************************************************** 24 | */ 25 | public class FollowersViewModelOld extends BaseViewModel { 26 | 27 | private final int pageSize = 10;//分页 28 | private long pageIndex;//索引 29 | 30 | public MutableLiveData followersResultCode = new MutableLiveData<>();//列表请求 0成功;other失败 31 | public String followersResultMsg; 32 | public boolean followersRefresh;//true 下拉刷新;false 上拉加载 33 | public boolean followersNewEmpty;//true 增量为空;false 增量不为空 34 | public List mData = new ArrayList<>();//列表数据 35 | 36 | 37 | public FollowersViewModelOld() { 38 | mModel = new MyModel(); 39 | } 40 | 41 | /** 42 | * 获取列表 43 | * 44 | * @param isRefresh true 下拉刷新;false 上拉加载 45 | */ 46 | public void getFollowers(boolean isRefresh) { 47 | if (isRefresh) { 48 | pageIndex = 1; 49 | } 50 | String userName = SPUtil.getInstance().getString(Constant.USER_NAME); 51 | ((MyModel) mModel).getFollowers(userName, pageSize, pageIndex, new HttpListener() { 52 | @Override 53 | public void onSuccess(Object object) { 54 | List list = (List) object; 55 | pageIndex++;//偏移量+1 56 | if (isRefresh) //下拉覆盖,上拉增量 57 | mData.clear(); 58 | if (!list.isEmpty()) 59 | mData.addAll(list); 60 | followersRefresh = isRefresh; 61 | followersNewEmpty = list.isEmpty(); 62 | followersResultCode.postValue("0"); 63 | } 64 | 65 | @Override 66 | public void onError(String errorMsg, String code) { 67 | followersResultMsg = errorMsg; 68 | followersRefresh = isRefresh; 69 | followersResultCode.postValue(code); 70 | } 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/follow/ProfileDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.follow; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | import android.view.View; 10 | 11 | import androidx.lifecycle.ViewModelProvider; 12 | 13 | import com.bumptech.glide.Glide; 14 | import com.bumptech.glide.load.resource.bitmap.CircleCrop; 15 | import com.bumptech.glide.request.RequestOptions; 16 | import com.hqumath.androidmvvm.base.BaseActivity; 17 | import com.hqumath.androidmvvm.databinding.ActivityProfileDetailBinding; 18 | import com.hqumath.androidmvvm.utils.CommonUtil; 19 | 20 | /** 21 | * **************************************************************** 22 | * 文件名称: ProfileActivity 23 | * 作 者: Created by gyd 24 | * 创建时间: 2019/7/26 10:04 25 | * 文件描述: 26 | * 注意事项: 27 | * 版权声明: 28 | * **************************************************************** 29 | */ 30 | public class ProfileDetailActivity extends BaseActivity { 31 | 32 | private ActivityProfileDetailBinding binding; 33 | private ProfileDetailViewModel viewModel; 34 | 35 | public static Intent getStartIntent(Context mContext, String mUserName) { 36 | Intent intent = new Intent(mContext, ProfileDetailActivity.class); 37 | intent.putExtra("UserName", mUserName); 38 | return intent; 39 | } 40 | 41 | @Override 42 | public View initContentView(Bundle savedInstanceState) { 43 | binding = ActivityProfileDetailBinding.inflate(getLayoutInflater()); 44 | binding.setLifecycleOwner(this); 45 | return binding.getRoot(); 46 | } 47 | 48 | @Override 49 | protected void initListener() { 50 | //状态栏透明 51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 52 | getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 53 | getWindow().setStatusBarColor(Color.TRANSPARENT); 54 | } 55 | setSupportActionBar(binding.toolbar); 56 | binding.toolbar.setNavigationOnClickListener(v -> finish()); 57 | binding.refreshLayout.setOnRefreshListener(v -> { 58 | viewModel.getUserInfo(); 59 | }); 60 | } 61 | 62 | @Override 63 | public void initData() { 64 | viewModel = new ViewModelProvider(this).get(ProfileDetailViewModel.class); 65 | binding.setViewModel(viewModel); 66 | //data 67 | viewModel.userName = getIntent().getStringExtra("UserName"); 68 | setTitle(viewModel.userName); 69 | //详情 70 | binding.refreshLayout.autoRefresh();//触发自动刷新 71 | } 72 | 73 | public void initViewObservable() { 74 | viewModel.userInfoResultCode.observe(this, code -> { 75 | if (code.equals("0")) { 76 | if (!TextUtils.isEmpty(viewModel.avatar_url)) { 77 | Glide.with(mContext).load(viewModel.avatar_url).into(binding.ivAvatarBg); 78 | Glide.with(mContext).load(viewModel.avatar_url) 79 | .apply(RequestOptions.bitmapTransform(new CircleCrop()))//圆形 80 | .into(binding.ivAvatar); 81 | binding.refreshLayout.finishRefresh(); 82 | } 83 | } else { 84 | CommonUtil.toast(viewModel.userInfoResultMsg); 85 | binding.refreshLayout.finishRefresh(false); 86 | } 87 | }); 88 | viewModel.company.observe(this, value -> binding.tvCompany.setVisibility(TextUtils.isEmpty(value) ? View.GONE : View.VISIBLE)); 89 | viewModel.email.observe(this, value -> binding.tvEmail.setVisibility(TextUtils.isEmpty(value) ? View.GONE : View.VISIBLE)); 90 | viewModel.blog.observe(this, value -> binding.tvBlog.setVisibility(TextUtils.isEmpty(value) ? View.GONE : View.VISIBLE)); 91 | } 92 | 93 | @Override 94 | public void onDestroy() { 95 | if (viewModel != null) { 96 | viewModel.dispose(); 97 | viewModel = null; 98 | } 99 | super.onDestroy(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/follow/ProfileDetailViewModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.follow; 2 | 3 | import androidx.lifecycle.MutableLiveData; 4 | 5 | import com.hqumath.androidmvvm.base.BaseViewModel; 6 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 7 | import com.hqumath.androidmvvm.net.HttpListener; 8 | import com.hqumath.androidmvvm.repository.MyModel; 9 | 10 | import java.text.ParseException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | 14 | /** 15 | * **************************************************************** 16 | * 文件名称: ProfileDetailViewModel 17 | * 作 者: Created by gyd 18 | * 创建时间: 2019/7/17 11:52 19 | * 文件描述: 20 | * 注意事项: 21 | * 版权声明: 22 | * **************************************************************** 23 | */ 24 | public class ProfileDetailViewModel extends BaseViewModel { 25 | 26 | public String userName; 27 | //用户信息 28 | public MutableLiveData userInfoResultCode = new MutableLiveData<>();//0成功;other失败 29 | public String userInfoResultMsg; 30 | public String avatar_url; 31 | public MutableLiveData location = new MutableLiveData<>(); 32 | public MutableLiveData created_at = new MutableLiveData<>(); 33 | public MutableLiveData name = new MutableLiveData<>(); 34 | public MutableLiveData company = new MutableLiveData<>(); 35 | public MutableLiveData email = new MutableLiveData<>(); 36 | public MutableLiveData blog = new MutableLiveData<>(); 37 | 38 | public ProfileDetailViewModel() { 39 | mModel = new MyModel(); 40 | } 41 | 42 | /** 43 | * 获取用户信息 44 | */ 45 | public void getUserInfo() { 46 | ((MyModel) mModel).getUserInfo(userName, new HttpListener() { 47 | @Override 48 | public void onSuccess(Object object) { 49 | UserInfoEntity data = (UserInfoEntity) object; 50 | avatar_url = data.getAvatar_url(); 51 | location.postValue(data.getLocation()); 52 | //时间格式化 53 | String date = data.getCreated_at();//2011-12-29T04:45:11Z 54 | date = date.replace("Z", " UTC");//UTC是世界标准时间 55 | SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss Z"); 56 | SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 57 | try { 58 | Date date1 = format1.parse(date); 59 | String date2 = format2.format(date1); 60 | created_at.postValue(date2); 61 | } catch (ParseException e) { 62 | e.printStackTrace(); 63 | } 64 | name.postValue(data.getName()); 65 | company.postValue(data.getCompany()); 66 | email.postValue(data.getEmail()); 67 | blog.postValue(data.getBlog()); 68 | userInfoResultCode.postValue("0"); 69 | } 70 | 71 | @Override 72 | public void onError(String errorMsg, String code) { 73 | userInfoResultMsg = errorMsg; 74 | userInfoResultCode.postValue(code); 75 | } 76 | }); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/login/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.login; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | import android.view.View; 7 | import android.view.inputmethod.EditorInfo; 8 | 9 | import androidx.lifecycle.ViewModelProvider; 10 | 11 | import com.hqumath.androidmvvm.R; 12 | import com.hqumath.androidmvvm.base.BaseActivity; 13 | import com.hqumath.androidmvvm.databinding.ActivityLoginBinding; 14 | import com.hqumath.androidmvvm.ui.main.MainActivity; 15 | import com.hqumath.androidmvvm.utils.CommonUtil; 16 | 17 | /** 18 | * **************************************************************** 19 | * 文件名称: LoginActivity 20 | * 作 者: Created by gyd 21 | * 创建时间: 2019/6/3 10:26 22 | * 文件描述: 23 | * 注意事项: 24 | * 版权声明: 25 | * **************************************************************** 26 | */ 27 | public class LoginActivity extends BaseActivity { 28 | 29 | private ActivityLoginBinding binding; 30 | private LoginViewModel viewModel; 31 | 32 | @Override 33 | protected View initContentView(Bundle savedInstanceState) { 34 | binding = ActivityLoginBinding.inflate(getLayoutInflater()); 35 | binding.setLifecycleOwner(this); 36 | return binding.getRoot(); 37 | } 38 | 39 | @Override 40 | protected void initListener() { 41 | binding.etPwd.setOnEditorActionListener((v, actionId, event) -> { 42 | if (actionId == EditorInfo.IME_ACTION_GO) { 43 | loginRequest(); 44 | return true; 45 | } 46 | return false; 47 | }); 48 | binding.btnLogin.setOnClickListener(v -> { 49 | loginRequest(); 50 | }); 51 | } 52 | 53 | @Override 54 | protected void initData() { 55 | viewModel = new ViewModelProvider(this).get(LoginViewModel.class); 56 | binding.setViewModel(viewModel); 57 | } 58 | 59 | @Override 60 | protected void initViewObservable() { 61 | viewModel.loginResultCode.observe(this, code -> { 62 | if (code.equals("0")) { 63 | if (viewModel.loginResultData != null) { 64 | CommonUtil.toast(viewModel.loginResultData.getName() + "已登录"); 65 | } 66 | startActivity(new Intent(mContext, MainActivity.class)); 67 | finish(); 68 | } else { 69 | if (!TextUtils.isEmpty(viewModel.loginResultMsg)) { 70 | CommonUtil.toast(viewModel.loginResultMsg); 71 | } 72 | binding.btnLogin.setEnabled(true); 73 | } 74 | }); 75 | viewModel.isLoading.observe(this, b -> { 76 | if (b) { 77 | showProgressDialog("loading"); 78 | } else { 79 | dismissProgressDialog(); 80 | } 81 | }); 82 | } 83 | 84 | @Override 85 | protected void onDestroy() { 86 | if (viewModel != null) { 87 | viewModel.dispose(); 88 | viewModel = null; 89 | } 90 | super.onDestroy(); 91 | } 92 | 93 | /** 94 | * 登录请求 95 | */ 96 | private void loginRequest() { 97 | boolean valid = true; 98 | if (TextUtils.isEmpty(viewModel.userName.getValue())) { 99 | valid = false; 100 | binding.llName.setError(getString(R.string.user_name_warning)); 101 | } else { 102 | binding.llName.setErrorEnabled(false); 103 | } 104 | if (TextUtils.isEmpty(viewModel.password.getValue())) { 105 | valid = false; 106 | binding.llPwd.setError(getString(R.string.password_warning)); 107 | } else { 108 | binding.llPwd.setErrorEnabled(false); 109 | } 110 | if (valid) { 111 | binding.btnLogin.setEnabled(false); 112 | viewModel.login(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/login/LoginViewModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.login; 2 | 3 | import android.text.TextUtils; 4 | 5 | import androidx.lifecycle.MutableLiveData; 6 | 7 | import com.hqumath.androidmvvm.app.Constant; 8 | import com.hqumath.androidmvvm.base.BaseViewModel; 9 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 10 | import com.hqumath.androidmvvm.net.HttpListener; 11 | import com.hqumath.androidmvvm.repository.MyModel; 12 | import com.hqumath.androidmvvm.utils.SPUtil; 13 | 14 | /** 15 | * **************************************************************** 16 | * 文件名称: LoginViewModel 17 | * 作 者: Created by gyd 18 | * 创建时间: 2019/6/3 10:27 19 | * 文件描述: 20 | * 注意事项: 21 | * 版权声明: 22 | * **************************************************************** 23 | */ 24 | public class LoginViewModel extends BaseViewModel { 25 | 26 | public MutableLiveData isLoading = new MutableLiveData<>(); 27 | public MutableLiveData userName = new MutableLiveData<>(); 28 | public MutableLiveData password = new MutableLiveData<>(); 29 | //登录 30 | public MutableLiveData loginResultCode = new MutableLiveData<>();//0成功;other失败 31 | public String loginResultMsg; 32 | public UserInfoEntity loginResultData; 33 | 34 | public LoginViewModel() { 35 | mModel = new MyModel(); 36 | //自动登录 37 | String name = SPUtil.getInstance().getString(Constant.USER_NAME); 38 | if (!TextUtils.isEmpty(name)) { 39 | loginResultCode.setValue("0"); 40 | } else { 41 | //测试数据 42 | userName.setValue("JakeWharton"); 43 | password.setValue("1234"); 44 | } 45 | } 46 | 47 | public void login() { 48 | //模拟登陆接口 49 | isLoading.setValue(true); 50 | ((MyModel) mModel).login(userName.getValue(), password.getValue(), new HttpListener() { 51 | @Override 52 | public void onSuccess(Object object) { 53 | isLoading.postValue(false); 54 | loginResultData = (UserInfoEntity) object; 55 | loginResultCode.postValue("0"); 56 | } 57 | 58 | @Override 59 | public void onError(String errorMsg, String code) { 60 | isLoading.postValue(false); 61 | loginResultMsg = errorMsg; 62 | loginResultCode.postValue(code); 63 | } 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/main/AboutFragment.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.main; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.hqumath.androidmvvm.base.BaseFragment; 9 | import com.hqumath.androidmvvm.databinding.FragmentAboutBinding; 10 | import com.hqumath.androidmvvm.ui.follow.ProfileDetailActivity; 11 | import com.hqumath.androidmvvm.ui.repos.ReposDetailActivity; 12 | import com.hqumath.androidmvvm.utils.CommonUtil; 13 | 14 | /** 15 | * **************************************************************** 16 | * 文件名称: AboutFragment 17 | * 作 者: Created by gyd 18 | * 创建时间: 2019/11/5 10:06 19 | * 文件描述: 20 | * 注意事项: 21 | * **************************************************************** 22 | */ 23 | public class AboutFragment extends BaseFragment { 24 | 25 | private FragmentAboutBinding binding; 26 | 27 | @Override 28 | protected View initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 29 | binding = FragmentAboutBinding.inflate(inflater, container, false); 30 | return binding.getRoot(); 31 | } 32 | 33 | @Override 34 | protected void initListener() { 35 | binding.llSourcecode.setOnClickListener(v -> { 36 | startActivity(ReposDetailActivity.getStartIntent(mContext, "androidmvvm", "yadiq")); 37 | }); 38 | binding.llProfile.setOnClickListener(v -> { 39 | startActivity(ProfileDetailActivity.getStartIntent(mContext, "yadiq")); 40 | }); 41 | } 42 | 43 | @Override 44 | protected void initData() { 45 | binding.tvVersion.setText(CommonUtil.getVersion()); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.main; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import androidx.viewpager.widget.ViewPager; 7 | 8 | import com.hqumath.androidmvvm.adapter.MyFragmentPagerAdapter; 9 | import com.hqumath.androidmvvm.base.BaseActivity; 10 | import com.hqumath.androidmvvm.base.BaseFragment; 11 | import com.hqumath.androidmvvm.databinding.ActivityMainBinding; 12 | import com.hqumath.androidmvvm.ui.follow.FollowersFragment; 13 | import com.hqumath.androidmvvm.ui.repos.ReposFragment; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class MainActivity extends BaseActivity { 19 | 20 | private ActivityMainBinding binding; 21 | 22 | @Override 23 | public View initContentView(Bundle savedInstanceState) { 24 | binding = ActivityMainBinding.inflate(getLayoutInflater()); 25 | return binding.getRoot(); 26 | } 27 | 28 | @Override 29 | protected void initListener() { 30 | } 31 | 32 | @Override 33 | protected void initData() { 34 | List fragmentList = new ArrayList<>(); 35 | fragmentList.add(new ReposFragment()); 36 | fragmentList.add(new FollowersFragment()); 37 | fragmentList.add(new SettingsFragment()); 38 | fragmentList.add(new AboutFragment()); 39 | 40 | MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager()); 41 | pagerAdapter.setData(fragmentList, null); 42 | binding.viewPager.setAdapter(pagerAdapter); 43 | binding.viewPager.setOffscreenPageLimit(3);//缓存当前界面每一侧的界面数 44 | binding.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 45 | @Override 46 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 47 | } 48 | 49 | @Override 50 | public void onPageSelected(int position) { 51 | binding.navigation.getMenu().getItem(position).setChecked(true); 52 | } 53 | 54 | @Override 55 | public void onPageScrollStateChanged(int state) { 56 | } 57 | }); 58 | binding.navigation.setOnNavigationItemSelectedListener(item -> { 59 | binding.viewPager.setCurrentItem(item.getOrder()); 60 | return true; 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/main/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.main; 2 | 3 | import static com.hqumath.androidmvvm.utils.CommonUtil.toast; 4 | 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.hqumath.androidmvvm.app.Constant; 12 | import com.hqumath.androidmvvm.base.BaseFragment; 13 | import com.hqumath.androidmvvm.databinding.FragmentSettingsBinding; 14 | import com.hqumath.androidmvvm.service.UpdateService; 15 | import com.hqumath.androidmvvm.ui.fileupdown.FileUpDownActivity; 16 | import com.hqumath.androidmvvm.ui.login.LoginActivity; 17 | import com.hqumath.androidmvvm.utils.DialogUtil; 18 | import com.hqumath.androidmvvm.utils.SPUtil; 19 | 20 | import java.util.Random; 21 | 22 | public class SettingsFragment extends BaseFragment { 23 | 24 | private FragmentSettingsBinding binding; 25 | 26 | @Override 27 | protected View initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 28 | binding = FragmentSettingsBinding.inflate(inflater, container, false); 29 | return binding.getRoot(); 30 | } 31 | 32 | @Override 33 | protected void initListener() { 34 | binding.fileUpDown.setOnClickListener(v -> { 35 | Intent intent = new Intent(mContext, FileUpDownActivity.class); 36 | startActivity(intent); 37 | }); 38 | binding.vCheckUpdate.setOnClickListener(v -> { 39 | //检查升级 40 | boolean needUpdate = new Random().nextBoolean(); 41 | if (!needUpdate) { 42 | toast("已是最新版本"); 43 | return; 44 | } 45 | SPUtil.getInstance().put(Constant.APK_URL, FileUpDownActivity.url); 46 | SPUtil.getInstance().put(Constant.APK_NAME, "AndroidMVP V2.0"); 47 | DialogUtil alterDialogUtils = new DialogUtil(mContext); 48 | alterDialogUtils.setTitle("新版本V2.0"); 49 | alterDialogUtils.setMessage("适配 Android 11"); 50 | alterDialogUtils.setTwoConfirmBtn("立即更新", v1 -> { 51 | toast("已在后台下载"); 52 | mContext.startService(new Intent(mContext, UpdateService.class)); 53 | }); 54 | alterDialogUtils.setTwoCancelBtn("下次提醒", v2 -> { 55 | }); 56 | alterDialogUtils.setCancelable(false); 57 | alterDialogUtils.show(); 58 | }); 59 | binding.vLogout.setOnClickListener(v -> { 60 | SPUtil.getInstance().clear(); 61 | startActivity(new Intent(mContext, LoginActivity.class)); 62 | mContext.finish(); 63 | }); 64 | } 65 | 66 | @Override 67 | protected void initData() { 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/repos/MyReposFragment.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.repos; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.lifecycle.ViewModelProvider; 9 | 10 | import com.hqumath.androidmvvm.adapter.MyRecyclerAdapters; 11 | import com.hqumath.androidmvvm.base.BaseFragment; 12 | import com.hqumath.androidmvvm.bean.ReposEntity; 13 | import com.hqumath.androidmvvm.databinding.FragmentSwipeListBinding; 14 | import com.hqumath.androidmvvm.utils.CommonUtil; 15 | 16 | /** 17 | * **************************************************************** 18 | * 文件名称: MyReposFragment 19 | * 作 者: Created by gyd 20 | * 创建时间: 2019/11/5 10:06 21 | * 文件描述: 22 | * 注意事项: 23 | * 版权声明: 24 | * **************************************************************** 25 | */ 26 | public class MyReposFragment extends BaseFragment { 27 | 28 | private FragmentSwipeListBinding binding; 29 | private ReposViewModel viewModel; 30 | private MyRecyclerAdapters.ReposRecyclerAdapter recyclerAdapter; 31 | protected boolean hasRequested;//在onResume中判断是否已经请求过数据。用于懒加载 32 | 33 | @Override 34 | protected View initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 35 | binding = FragmentSwipeListBinding.inflate(inflater, container, false); 36 | return binding.getRoot(); 37 | } 38 | 39 | @Override 40 | protected void initListener() { 41 | binding.refreshLayout.setOnRefreshListener(v -> viewModel.getMyRepos(true)); 42 | binding.refreshLayout.setOnLoadMoreListener(v -> viewModel.getMyRepos(false)); 43 | } 44 | 45 | @Override 46 | protected void initData() { 47 | viewModel = new ViewModelProvider(requireActivity()).get(ReposViewModel.class); 48 | 49 | recyclerAdapter = new MyRecyclerAdapters.ReposRecyclerAdapter(mContext, viewModel.myReposData); 50 | recyclerAdapter.setOnItemClickListener((v, position) -> { 51 | ReposEntity data = viewModel.myReposData.get(position); 52 | startActivity(ReposDetailActivity.getStartIntent(mContext, data.getName(), data.getOwner().getLogin())); 53 | }); 54 | binding.recyclerView.setAdapter(recyclerAdapter); 55 | } 56 | 57 | @Override 58 | protected void initViewObservable() { 59 | viewModel.myReposResultCode.observe(this, code -> { 60 | if (code.equals("0")) { 61 | recyclerAdapter.notifyDataSetChanged(); 62 | if (viewModel.myReposRefresh) { 63 | if (viewModel.myReposNewEmpty) { 64 | binding.refreshLayout.finishRefreshWithNoMoreData();//上拉加载功能将显示没有更多数据 65 | } else { 66 | binding.refreshLayout.finishRefresh(); 67 | } 68 | } else { 69 | if (viewModel.myReposNewEmpty) { 70 | binding.refreshLayout.finishLoadMoreWithNoMoreData();//上拉加载功能将显示没有更多数据 71 | } else { 72 | binding.refreshLayout.finishLoadMore(); 73 | } 74 | } 75 | } else { 76 | CommonUtil.toast(viewModel.myReposResultMsg); 77 | if (viewModel.myReposRefresh) { 78 | binding.refreshLayout.finishRefresh(false);//刷新失败,会影响到上次的更新时间 79 | } else { 80 | binding.refreshLayout.finishLoadMore(false); 81 | } 82 | } 83 | binding.emptyLayout.llEmpty.setVisibility(viewModel.myReposData.isEmpty() ? View.VISIBLE : View.GONE); 84 | }); 85 | } 86 | 87 | @Override 88 | public void onResume() { 89 | super.onResume(); 90 | if (!hasRequested) { 91 | hasRequested = true; 92 | binding.refreshLayout.autoRefresh();//触发自动刷新 93 | } 94 | } 95 | 96 | @Override 97 | public void onDestroy() { 98 | if (viewModel != null) { 99 | viewModel.dispose(); 100 | viewModel = null; 101 | } 102 | super.onDestroy(); 103 | } 104 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/repos/ReposDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.repos; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | import android.view.View; 10 | 11 | import androidx.lifecycle.ViewModelProvider; 12 | 13 | import com.bumptech.glide.Glide; 14 | import com.hqumath.androidmvvm.adapter.MyRecyclerAdapters; 15 | import com.hqumath.androidmvvm.base.BaseActivity; 16 | import com.hqumath.androidmvvm.databinding.ActivityReposDetailBinding; 17 | import com.hqumath.androidmvvm.utils.CommonUtil; 18 | 19 | /** 20 | * **************************************************************** 21 | * 文件名称: ReposDetailActivity 22 | * 作 者: Created by gyd 23 | * 创建时间: 2020/9/4 16:33 24 | * 文件描述: 25 | * 注意事项: 26 | * **************************************************************** 27 | */ 28 | public class ReposDetailActivity extends BaseActivity { 29 | private ActivityReposDetailBinding binding; 30 | private ReposDetailViewModel viewModel; 31 | private MyRecyclerAdapters.CommitsRecyclerAdapter recyclerAdapter; 32 | 33 | public static Intent getStartIntent(Context mContext, String mReposName, String mUserName) { 34 | Intent intent = new Intent(mContext, ReposDetailActivity.class); 35 | intent.putExtra("ReposName", mReposName); 36 | intent.putExtra("UserName", mUserName); 37 | return intent; 38 | } 39 | 40 | @Override 41 | public View initContentView(Bundle savedInstanceState) { 42 | binding = ActivityReposDetailBinding.inflate(getLayoutInflater()); 43 | binding.setLifecycleOwner(this); 44 | return binding.getRoot(); 45 | } 46 | 47 | @Override 48 | protected void initListener() { 49 | //状态栏透明 50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 51 | getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 52 | getWindow().setStatusBarColor(Color.TRANSPARENT); 53 | } 54 | setSupportActionBar(binding.toolbar); 55 | binding.toolbar.setNavigationOnClickListener(v -> finish()); 56 | binding.refreshLayout.setOnRefreshListener(v -> { 57 | viewModel.getReposInfo(); 58 | viewModel.getCommits(true); 59 | }); 60 | binding.refreshLayout.setOnLoadMoreListener(v -> viewModel.getCommits(false)); 61 | } 62 | 63 | @Override 64 | protected void initData() { 65 | viewModel = new ViewModelProvider(this).get(ReposDetailViewModel.class); 66 | binding.setViewModel(viewModel); 67 | 68 | //data 69 | viewModel.userName = getIntent().getStringExtra("UserName"); 70 | viewModel.reposName = getIntent().getStringExtra("ReposName"); 71 | setTitle(viewModel.reposName); 72 | 73 | recyclerAdapter = new MyRecyclerAdapters.CommitsRecyclerAdapter(mContext, viewModel.commitData); 74 | binding.recyclerView.setAdapter(recyclerAdapter); 75 | //仓库详情 76 | viewModel.getReposInfo(); 77 | //提交记录 78 | binding.refreshLayout.autoRefresh();//触发自动刷新 79 | } 80 | 81 | @Override 82 | protected void initViewObservable() { 83 | viewModel.reposResultCode.observe(this, code -> { 84 | if (code.equals("0")) { 85 | if (!TextUtils.isEmpty(viewModel.avatar_url)) 86 | Glide.with(mContext).load(viewModel.avatar_url).into(binding.ivAvatarBg); 87 | } else { 88 | CommonUtil.toast(viewModel.reposResultMsg); 89 | } 90 | }); 91 | 92 | viewModel.commitResultCode.observe(this, code -> { 93 | if (code.equals("0")) { 94 | recyclerAdapter.notifyDataSetChanged(); 95 | if (viewModel.commitRefresh) { 96 | if (viewModel.commitNewEmpty) { 97 | binding.refreshLayout.finishRefreshWithNoMoreData();//上拉加载功能将显示没有更多数据 98 | } else { 99 | binding.refreshLayout.finishRefresh(); 100 | } 101 | } else { 102 | if (viewModel.commitNewEmpty) { 103 | binding.refreshLayout.finishLoadMoreWithNoMoreData();//上拉加载功能将显示没有更多数据 104 | } else { 105 | binding.refreshLayout.finishLoadMore(); 106 | } 107 | } 108 | } else { 109 | CommonUtil.toast(viewModel.commitResultMsg); 110 | if (viewModel.commitRefresh) { 111 | binding.refreshLayout.finishRefresh(false);//刷新失败,会影响到上次的更新时间 112 | } else { 113 | binding.refreshLayout.finishLoadMore(false); 114 | } 115 | } 116 | binding.emptyLayout.llEmpty.setVisibility(viewModel.commitData.isEmpty() ? View.VISIBLE : View.GONE); 117 | }); 118 | } 119 | 120 | @Override 121 | public void onDestroy() { 122 | if (viewModel != null) { 123 | viewModel.dispose(); 124 | viewModel = null; 125 | } 126 | super.onDestroy(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/repos/ReposDetailViewModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.repos; 2 | 3 | import androidx.lifecycle.MutableLiveData; 4 | 5 | import com.hqumath.androidmvvm.base.BaseViewModel; 6 | import com.hqumath.androidmvvm.bean.CommitEntity; 7 | import com.hqumath.androidmvvm.bean.ReposEntity; 8 | import com.hqumath.androidmvvm.net.HttpListener; 9 | import com.hqumath.androidmvvm.repository.MyModel; 10 | import com.hqumath.androidmvvm.utils.CommonUtil; 11 | import com.hqumath.androidmvvm.utils.StringUtil; 12 | 13 | import java.text.ParseException; 14 | import java.text.SimpleDateFormat; 15 | import java.util.ArrayList; 16 | import java.util.Date; 17 | import java.util.List; 18 | import java.util.Locale; 19 | 20 | public class ReposDetailViewModel extends BaseViewModel { 21 | 22 | private final static int pageSize = 10;//分页 23 | public String userName; 24 | public String reposName; 25 | //仓库详情 26 | public MutableLiveData reposResultCode = new MutableLiveData<>();//0成功;other失败 27 | public String reposResultMsg; 28 | public String avatar_url; 29 | public MutableLiveData description = new MutableLiveData<>(); 30 | public MutableLiveData fullName = new MutableLiveData<>(); 31 | public MutableLiveData createdAt = new MutableLiveData<>(); 32 | public MutableLiveData languageSize = new MutableLiveData<>(); 33 | //提交记录列表 34 | private long commitPageIndex;//索引 35 | public MutableLiveData commitResultCode = new MutableLiveData<>();//0成功;other失败 36 | public String commitResultMsg; 37 | public boolean commitRefresh;//true 下拉刷新;false 上拉加载 38 | public boolean commitNewEmpty;//true 增量为空;false 增量不为空 39 | public List commitData = new ArrayList<>();//列表数据 40 | 41 | public ReposDetailViewModel() { 42 | mModel = new MyModel(); 43 | } 44 | 45 | public void getReposInfo() { 46 | ((MyModel) mModel).getReposInfo(userName, reposName, new HttpListener() { 47 | @Override 48 | public void onSuccess(Object object) { 49 | ReposEntity data = (ReposEntity) object; 50 | avatar_url = data.getOwner().getAvatar_url(); 51 | description.postValue(data.getDescription()); 52 | fullName.postValue(data.getFull_name()); 53 | //时间格式化 54 | String date = data.getCreated_at();//2011-12-29T04:45:11Z 55 | date = date.replace("Z", " UTC");//UTC是世界标准时间 56 | SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss Z"); 57 | SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 58 | try { 59 | Date date1 = format1.parse(date); 60 | String date2 = format2.format(date1); 61 | createdAt.postValue(date2); 62 | } catch (ParseException e) { 63 | e.printStackTrace(); 64 | } 65 | String info = String.format(Locale.getDefault(), "Language %s, size %s", 66 | data.getLanguage(), StringUtil.getSizeString(data.getSize() * 1024)); 67 | languageSize.postValue(info); 68 | reposResultCode.postValue("0"); 69 | } 70 | 71 | @Override 72 | public void onError(String errorMsg, String code) { 73 | reposResultMsg = errorMsg; 74 | reposResultCode.postValue(code); 75 | } 76 | }); 77 | } 78 | 79 | /** 80 | * 获取列表 81 | * 82 | * @param isRefresh true 下拉刷新;false 上拉加载 83 | */ 84 | public void getCommits(boolean isRefresh) { 85 | if (isRefresh) { 86 | commitPageIndex = 1; 87 | } 88 | ((MyModel) mModel).getCommits(userName, reposName, pageSize, commitPageIndex, new HttpListener() { 89 | @Override 90 | public void onSuccess(Object object) { 91 | List list = (List) object; 92 | commitPageIndex++;//偏移量+1 93 | if (isRefresh) //下拉覆盖,上拉增量 94 | commitData.clear(); 95 | if (!list.isEmpty()) 96 | commitData.addAll(list); 97 | commitRefresh = isRefresh; 98 | commitNewEmpty = list.isEmpty(); 99 | commitResultCode.postValue("0"); 100 | } 101 | 102 | @Override 103 | public void onError(String errorMsg, String code) { 104 | commitResultMsg = errorMsg; 105 | commitRefresh = isRefresh; 106 | commitResultCode.postValue(code); 107 | } 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/repos/ReposFragment.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.repos; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.hqumath.androidmvvm.adapter.MyFragmentPagerAdapter; 9 | import com.hqumath.androidmvvm.base.BaseFragment; 10 | import com.hqumath.androidmvvm.databinding.FragmentReposBinding; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * **************************************************************** 17 | * 文件名称: OneFragment 18 | * 作 者: Created by gyd 19 | * 创建时间: 2019/11/5 10:06 20 | * 文件描述: 21 | * 注意事项: 22 | * 版权声明: 23 | * **************************************************************** 24 | */ 25 | public class ReposFragment extends BaseFragment { 26 | 27 | private FragmentReposBinding binding; 28 | 29 | @Override 30 | public View initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 31 | binding = FragmentReposBinding.inflate(inflater, container, false); 32 | return binding.getRoot(); 33 | } 34 | 35 | @Override 36 | protected void initListener() { 37 | } 38 | 39 | @Override 40 | protected void initData() { 41 | List fragmentList = new ArrayList<>(); 42 | fragmentList.add(new MyReposFragment()); 43 | fragmentList.add(new StarredFragment()); 44 | 45 | List titles = new ArrayList<>(); 46 | titles.add("MyRepos"); 47 | titles.add("Starred"); 48 | 49 | MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(getChildFragmentManager()); 50 | pagerAdapter.setData(fragmentList, titles); 51 | binding.viewPager.setAdapter(pagerAdapter); 52 | binding.viewPager.setOffscreenPageLimit(fragmentList.size()); 53 | binding.tabLayout.setupWithViewPager(binding.viewPager); 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/repos/ReposViewModel.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.repos; 2 | 3 | import androidx.lifecycle.MutableLiveData; 4 | 5 | import com.hqumath.androidmvvm.app.Constant; 6 | import com.hqumath.androidmvvm.base.BaseViewModel; 7 | import com.hqumath.androidmvvm.bean.ReposEntity; 8 | import com.hqumath.androidmvvm.bean.UserInfoEntity; 9 | import com.hqumath.androidmvvm.net.HttpListener; 10 | import com.hqumath.androidmvvm.repository.MyModel; 11 | import com.hqumath.androidmvvm.utils.SPUtil; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * **************************************************************** 18 | * 文件名称: ReposViewModel 19 | * 作 者: Created by gyd 20 | * 创建时间: 2019/6/3 10:27 21 | * 文件描述: MyReposFragment和StarredFragment共用 22 | * 注意事项: 23 | * 版权声明: 24 | * **************************************************************** 25 | */ 26 | public class ReposViewModel extends BaseViewModel { 27 | 28 | private final int pageSize = 10;//分页 29 | //MyRepos 30 | private long myReposPageIndex;//索引 31 | public MutableLiveData myReposResultCode = new MutableLiveData<>();//0成功;other失败 32 | public String myReposResultMsg; 33 | public boolean myReposRefresh;//true 下拉刷新;false 上拉加载 34 | public boolean myReposNewEmpty;//true 增量为空;false 增量不为空 35 | public List myReposData = new ArrayList<>();//列表数据 36 | //Starred 37 | private long starredPageIndex;//索引 38 | public MutableLiveData starredResultCode = new MutableLiveData<>();//0成功;other失败 39 | public String starredResultMsg; 40 | public boolean starredRefresh;//true 下拉刷新;false 上拉加载 41 | public boolean starredNewEmpty;//true 增量为空;false 增量不为空 42 | public List starredData = new ArrayList<>();//列表数据 43 | 44 | public ReposViewModel() { 45 | mModel = new MyModel(); 46 | } 47 | 48 | /** 49 | * 获取列表 50 | * 51 | * @param isRefresh true 下拉刷新;false 上拉加载 52 | */ 53 | public void getMyRepos(boolean isRefresh) { 54 | if (isRefresh) { 55 | myReposPageIndex = 1; 56 | } 57 | String userName = SPUtil.getInstance().getString(Constant.USER_NAME); 58 | ((MyModel) mModel).getMyRepos(userName, pageSize, myReposPageIndex, new HttpListener() { 59 | @Override 60 | public void onSuccess(Object object) { 61 | List list = (List) object; 62 | myReposPageIndex++;//偏移量+1 63 | if (isRefresh) //下拉覆盖,上拉增量 64 | myReposData.clear(); 65 | if (!list.isEmpty()) 66 | myReposData.addAll(list); 67 | myReposRefresh = isRefresh; 68 | myReposNewEmpty = list.isEmpty(); 69 | myReposResultCode.postValue("0"); 70 | } 71 | 72 | @Override 73 | public void onError(String errorMsg, String code) { 74 | myReposResultMsg = errorMsg; 75 | myReposRefresh = isRefresh; 76 | myReposResultCode.postValue(code); 77 | } 78 | }); 79 | } 80 | 81 | /** 82 | * 获取列表 83 | * 84 | * @param isRefresh true 下拉刷新;false 上拉加载 85 | */ 86 | public void getStarred(boolean isRefresh) { 87 | if (isRefresh) { 88 | starredPageIndex = 1; 89 | } 90 | String userName = SPUtil.getInstance().getString(Constant.USER_NAME); 91 | ((MyModel) mModel).getStarred(userName, pageSize, starredPageIndex, new HttpListener() { 92 | @Override 93 | public void onSuccess(Object object) { 94 | List list = (List) object; 95 | starredPageIndex++;//偏移量+1 96 | if (isRefresh) //下拉覆盖,上拉增量 97 | starredData.clear(); 98 | if (!list.isEmpty()) 99 | starredData.addAll(list); 100 | starredRefresh = isRefresh; 101 | starredNewEmpty = list.isEmpty(); 102 | starredResultCode.postValue("0"); 103 | } 104 | 105 | @Override 106 | public void onError(String errorMsg, String code) { 107 | starredResultMsg = errorMsg; 108 | starredRefresh = isRefresh; 109 | starredResultCode.postValue(code); 110 | } 111 | }); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/ui/repos/StarredFragment.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.ui.repos; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.lifecycle.ViewModelProvider; 9 | 10 | import com.hqumath.androidmvvm.adapter.MyRecyclerAdapters; 11 | import com.hqumath.androidmvvm.base.BaseFragment; 12 | import com.hqumath.androidmvvm.bean.ReposEntity; 13 | import com.hqumath.androidmvvm.databinding.FragmentSwipeListBinding; 14 | import com.hqumath.androidmvvm.utils.CommonUtil; 15 | 16 | /** 17 | * **************************************************************** 18 | * 文件名称: StarredFragment 19 | * 作 者: Created by gyd 20 | * 创建时间: 2019/11/5 10:06 21 | * 文件描述: 22 | * 注意事项: 23 | * 版权声明: 24 | * **************************************************************** 25 | */ 26 | public class StarredFragment extends BaseFragment { 27 | 28 | private FragmentSwipeListBinding binding; 29 | private ReposViewModel viewModel; 30 | private MyRecyclerAdapters.ReposRecyclerAdapter recyclerAdapter; 31 | protected boolean hasRequested;//在onResume中判断是否已经请求过数据。用于懒加载 32 | 33 | @Override 34 | protected View initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 35 | binding = FragmentSwipeListBinding.inflate(inflater, container, false); 36 | return binding.getRoot(); 37 | } 38 | 39 | @Override 40 | protected void initListener() { 41 | binding.refreshLayout.setOnRefreshListener(v -> viewModel.getStarred(true)); 42 | binding.refreshLayout.setOnLoadMoreListener(v -> viewModel.getStarred(false)); 43 | } 44 | 45 | @Override 46 | protected void initData() { 47 | viewModel = new ViewModelProvider(requireActivity()).get(ReposViewModel.class); 48 | 49 | recyclerAdapter = new MyRecyclerAdapters.ReposRecyclerAdapter(mContext, viewModel.starredData); 50 | recyclerAdapter.setOnItemClickListener((v, position) -> { 51 | ReposEntity data = viewModel.starredData.get(position); 52 | startActivity(ReposDetailActivity.getStartIntent(mContext, data.getName(), data.getOwner().getLogin())); 53 | }); 54 | binding.recyclerView.setAdapter(recyclerAdapter); 55 | } 56 | 57 | @Override 58 | protected void initViewObservable() { 59 | viewModel.starredResultCode.observe(this, code -> { 60 | if (code.equals("0")) { 61 | recyclerAdapter.notifyDataSetChanged(); 62 | if (viewModel.starredRefresh) { 63 | if (viewModel.starredNewEmpty) { 64 | binding.refreshLayout.finishRefreshWithNoMoreData();//上拉加载功能将显示没有更多数据 65 | } else { 66 | binding.refreshLayout.finishRefresh(); 67 | } 68 | } else { 69 | if (viewModel.starredNewEmpty) { 70 | binding.refreshLayout.finishLoadMoreWithNoMoreData();//上拉加载功能将显示没有更多数据 71 | } else { 72 | binding.refreshLayout.finishLoadMore(); 73 | } 74 | } 75 | } else { 76 | CommonUtil.toast(viewModel.starredResultMsg); 77 | if (viewModel.starredRefresh) { 78 | binding.refreshLayout.finishRefresh(false);//刷新失败,会影响到上次的更新时间 79 | } else { 80 | binding.refreshLayout.finishLoadMore(false); 81 | } 82 | } 83 | binding.emptyLayout.llEmpty.setVisibility(viewModel.starredData.isEmpty() ? View.VISIBLE : View.GONE); 84 | }); 85 | } 86 | @Override 87 | public void onResume() { 88 | super.onResume(); 89 | if (!hasRequested) { 90 | hasRequested = true; 91 | binding.refreshLayout.autoRefresh();//触发自动刷新 92 | } 93 | } 94 | 95 | @Override 96 | public void onDestroy() { 97 | if (viewModel != null) { 98 | viewModel.dispose(); 99 | viewModel = null; 100 | } 101 | super.onDestroy(); 102 | } 103 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/CommonUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.pm.PackageInfo; 7 | import android.content.pm.PackageManager; 8 | import android.net.ConnectivityManager; 9 | import android.net.NetworkInfo; 10 | import android.os.Looper; 11 | import android.util.TypedValue; 12 | import android.view.View; 13 | import android.view.inputmethod.InputMethodManager; 14 | import android.widget.EditText; 15 | import android.widget.Toast; 16 | 17 | import androidx.annotation.NonNull; 18 | 19 | /** 20 | * 常用工具类 21 | */ 22 | public class CommonUtil { 23 | 24 | @SuppressLint("StaticFieldLeak") 25 | private static Context context; 26 | 27 | private CommonUtil() { 28 | throw new UnsupportedOperationException("u can't instantiate me..."); 29 | } 30 | 31 | /** 32 | * 初始化工具类 33 | * 34 | * @param context 上下文 35 | */ 36 | public static void init(@NonNull final Context context) { 37 | CommonUtil.context = context.getApplicationContext(); 38 | } 39 | 40 | /** 41 | * 获取ApplicationContext 42 | * 43 | * @return ApplicationContext 44 | */ 45 | public static Context getContext() { 46 | if (context != null) { 47 | return context; 48 | } 49 | throw new NullPointerException("should be initialized in application"); 50 | } 51 | 52 | public static void toast(String s) { 53 | Toast.makeText(context, s, Toast.LENGTH_SHORT).show(); 54 | } 55 | 56 | public static void showKeyboard(Activity activity, EditText editText) { 57 | InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 58 | inputMethodManager.showSoftInput(editText, 0); 59 | } 60 | 61 | public static void closeKeyboard(Activity activity) { 62 | View view = activity.getWindow().peekDecorView(); 63 | if (view != null) { 64 | InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 65 | inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); 66 | } 67 | } 68 | 69 | /** 70 | * 检查是否有网络 71 | */ 72 | public static boolean isNetworkAvailable() { 73 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 74 | NetworkInfo info = cm.getActiveNetworkInfo(); 75 | return info != null && info.isAvailable(); 76 | } 77 | 78 | //获取软件版本号 79 | public static String getVersion() { 80 | try { 81 | PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 82 | return pi.versionName; 83 | } catch (PackageManager.NameNotFoundException e) { 84 | e.printStackTrace(); 85 | return ""; 86 | } 87 | } 88 | 89 | public static int dp2px(float dpValue) { 90 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, 91 | context.getResources().getDisplayMetrics()); 92 | } 93 | 94 | public static void logCurThread(String tag) { 95 | LogUtil.d("当前线程", tag + (Looper.myLooper() == Looper.getMainLooper() ? "主线程" : "工作线程")); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/Density.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.ComponentCallbacks; 6 | import android.content.res.Configuration; 7 | import android.os.Bundle; 8 | import android.util.DisplayMetrics; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | 13 | /** 14 | * **************************************************************** 15 | * 文件名称: Density 16 | * 作 者: Created by gyd 17 | * 创建时间: 2018/10/31 21:28 18 | * 文件描述: 今日头条屏幕适配方案 https://blog.csdn.net/u013000152/article/details/80855315 19 | * 注意事项: 根据ui图,设置屏幕最小宽度 20 | * **************************************************************** 21 | */ 22 | public class Density { 23 | private static float appDensity; 24 | private static float appScaledDensity; 25 | private static DisplayMetrics appDisplayMetrics; 26 | /** 27 | * 用来参照的的width 28 | */ 29 | private static float WIDTH; 30 | 31 | public static void setDensity(@NonNull final Application application, float width) { 32 | appDisplayMetrics = application.getResources().getDisplayMetrics(); 33 | WIDTH = width; 34 | registerActivityLifecycleCallbacks(application); 35 | 36 | if (appDensity == 0) { 37 | //初始化的时候赋值 38 | appDensity = appDisplayMetrics.density; 39 | appScaledDensity = appDisplayMetrics.scaledDensity; 40 | 41 | //添加字体变化的监听 42 | application.registerComponentCallbacks(new ComponentCallbacks() { 43 | @Override 44 | public void onConfigurationChanged(Configuration newConfig) { 45 | //字体改变后,将appScaledDensity重新赋值 46 | if (newConfig != null && newConfig.fontScale > 0) { 47 | appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity; 48 | } 49 | } 50 | 51 | @Override 52 | public void onLowMemory() { 53 | } 54 | }); 55 | } 56 | } 57 | 58 | private static void setAppOrientation(@Nullable Activity activity) { 59 | float targetDensity = 0; 60 | try { 61 | //使用宽高中的最小值计算最小宽度 62 | if (appDisplayMetrics.heightPixels > appDisplayMetrics.widthPixels) { 63 | targetDensity = appDisplayMetrics.widthPixels / WIDTH; 64 | } else { 65 | targetDensity = appDisplayMetrics.heightPixels / WIDTH; 66 | } 67 | } catch (NumberFormatException e) { 68 | e.printStackTrace(); 69 | } 70 | 71 | float targetScaledDensity = targetDensity * (appScaledDensity / appDensity); 72 | int targetDensityDpi = (int) (160 * targetDensity); 73 | //最后在这里将修改过后的值赋给系统参数,只修改Activity的density值 74 | DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics(); 75 | activityDisplayMetrics.density = targetDensity; 76 | activityDisplayMetrics.scaledDensity = targetScaledDensity; 77 | activityDisplayMetrics.densityDpi = targetDensityDpi; 78 | } 79 | 80 | private static void registerActivityLifecycleCallbacks(Application application) { 81 | application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 82 | @Override 83 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 84 | setAppOrientation(activity); 85 | } 86 | 87 | @Override 88 | public void onActivityStarted(Activity activity) { 89 | } 90 | 91 | @Override 92 | public void onActivityResumed(Activity activity) { 93 | } 94 | 95 | @Override 96 | public void onActivityPaused(Activity activity) { 97 | } 98 | 99 | @Override 100 | public void onActivityStopped(Activity activity) { 101 | } 102 | 103 | @Override 104 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 105 | } 106 | 107 | @Override 108 | public void onActivityDestroyed(Activity activity) { 109 | 110 | } 111 | }); 112 | } 113 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/DeviceUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | import static android.content.Context.TELEPHONY_SERVICE; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.content.Context; 7 | import android.net.wifi.WifiInfo; 8 | import android.net.wifi.WifiManager; 9 | import android.os.Build; 10 | import android.provider.Settings; 11 | import android.telephony.TelephonyManager; 12 | import android.text.TextUtils; 13 | 14 | import java.net.NetworkInterface; 15 | import java.util.Enumeration; 16 | 17 | public class DeviceUtil { 18 | 19 | /** 20 | * 获取imei SIM卡槽 21 | */ 22 | public static String getIMEI() { 23 | Context context = CommonUtil.getContext(); 24 | String imei = ""; 25 | try { 26 | TelephonyManager tm = (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); 27 | imei = tm.getDeviceId(); 28 | } catch (Exception e) { 29 | e.printStackTrace(); 30 | } 31 | return imei; 32 | } 33 | 34 | /** 35 | * 获取wifi网卡的mac地址,6.0以上特殊处理 36 | * 37 | * @return 38 | */ 39 | public static String getMac() { 40 | Context context = CommonUtil.getContext(); 41 | try { 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 43 | String str = getMacMoreThanM(); 44 | if (!TextUtils.isEmpty(str)) 45 | return str; 46 | } else { 47 | @SuppressLint("WifiManagerLeak") 48 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 49 | WifiInfo wifiInfo = wifiManager.getConnectionInfo(); 50 | if (wifiInfo != null) 51 | return wifiInfo.getMacAddress(); 52 | } 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } 56 | return null; 57 | } 58 | 59 | /** 60 | * android 6.0+获取wifi的mac地址 61 | * 62 | * @return 63 | */ 64 | private static String getMacMoreThanM() { 65 | try { 66 | //获取本机器所有的网络接口 67 | Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); 68 | while (enumeration.hasMoreElements()) { 69 | NetworkInterface networkInterface = (NetworkInterface) enumeration.nextElement(); 70 | // wlan0:无线网卡 eth0:以太网卡 (机顶盒厂家贴的mac是以太网卡) 71 | if (!networkInterface.getName().equals("wlan0")) { 72 | continue; 73 | } 74 | //获取硬件地址,一般是MAC 75 | byte[] arrayOfByte = networkInterface.getHardwareAddress(); 76 | if (arrayOfByte == null || arrayOfByte.length == 0) { 77 | continue; 78 | } 79 | return ByteUtil.byteToHex(arrayOfByte).toUpperCase(); 80 | } 81 | } catch (Exception e) { 82 | e.printStackTrace(); 83 | } 84 | return null; 85 | } 86 | 87 | /** 88 | * 设备首次运行的时候,系统会随机生成一64位的数字,恢复出厂设置后改变 89 | * 支持获取oaid的,优先获取oaid,需要集成aar https://github.com/haoguibao/OaidDemo/tree/master 90 | */ 91 | public static String getAndroidId() { 92 | return Settings.System.getString(CommonUtil.getContext().getContentResolver(), Settings.System.ANDROID_ID); 93 | } 94 | 95 | /** 96 | * 设备序列号,有些手机上会出现垃圾数据,比如红米手机返回的就是连续的非随机数 97 | */ 98 | public static String getSN() { 99 | return Build.SERIAL; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/DialogUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | import com.hqumath.androidmvvm.R; 11 | 12 | /** 13 | * **************************************************************** 14 | * 文件名称: AlterDialogTool 15 | * 作 者: Created by gyd 16 | * 创建时间: 2018/10/28 23:01 17 | * 文件描述: 通用弹窗界面 18 | * 注意事项: 19 | * 版权声明: 20 | * **************************************************************** 21 | */ 22 | 23 | public class DialogUtil extends Dialog { 24 | private Context mContext; 25 | private TextView tvTitle;//标题 26 | private TextView tvMessage;//内容 27 | private Button btnOneYes;//一个按钮时,确定按钮 28 | private View llTwo;//两个按钮布局 29 | private Button btnYes;//确定按钮 30 | private Button btnNo;//取消按钮 31 | private View line;//确定取消按钮中间竖线,有些样式没有 32 | private View ivRightClose;//右上角叉号 33 | private View mView; 34 | 35 | //ex. 36 | /*DialogUtil alterDialogUtils = new DialogUtil(mContext); 37 | alterDialogUtils.setTitle("提示"); 38 | alterDialogUtils.setMessage("是否确认退出驾驶?"); 39 | alterDialogUtils.setTwoConfirmBtn("确定", v -> {}); 40 | alterDialogUtils.setTwoCancelBtn("取消", v -> {}); 41 | alterDialogUtils.show();*/ 42 | //仅显示确定按钮 43 | //alterDialogUtils.setOneConfirmBtn("确定", v -> {}); 44 | 45 | /* 46 | * 默认主要操作弹窗 47 | */ 48 | public DialogUtil(Context context) { 49 | this(context, R.style.dialog_common, R.layout.dialog_common); 50 | } 51 | 52 | public DialogUtil(Context context, int theme, int messageLayout) { 53 | super(context, theme); 54 | this.mContext = context; 55 | mView = LayoutInflater.from(getContext()).inflate(messageLayout, null); 56 | tvTitle = (TextView) mView.findViewById(R.id.title); 57 | tvMessage = (TextView) mView.findViewById(R.id.message); 58 | btnOneYes = (Button) mView.findViewById(R.id.oneYes); 59 | llTwo = mView.findViewById(R.id.llTwo); 60 | btnYes = (Button) mView.findViewById(R.id.yes); 61 | btnNo = (Button) mView.findViewById(R.id.no); 62 | setContentView(mView); 63 | } 64 | 65 | public void setTitle(String title) { 66 | tvTitle.setText(title); 67 | } 68 | 69 | public void setMessage(int resId) { 70 | tvMessage.setText(resId); 71 | } 72 | 73 | public void setMessage(String message) { 74 | tvMessage.setText(message); 75 | } 76 | 77 | public void showRightClose() { 78 | if (ivRightClose != null) 79 | ivRightClose.setVisibility(View.VISIBLE); 80 | } 81 | 82 | public void setOneConfirmBtn(String text, View.OnClickListener listener) { 83 | setOneOrTwoBtn(true); 84 | if (text != null) { 85 | btnOneYes.setText(text); 86 | } 87 | btnOneYes.setOnClickListener(v -> { 88 | dismiss(); 89 | listener.onClick(v); 90 | }); 91 | } 92 | 93 | public void setTwoConfirmBtn(String text, View.OnClickListener listener) { 94 | setOneOrTwoBtn(false); 95 | if (text != null) { 96 | btnYes.setText(text); 97 | } 98 | btnYes.setOnClickListener(v -> { 99 | dismiss(); 100 | listener.onClick(v); 101 | }); 102 | } 103 | 104 | public void setTwoCancelBtn(String text, View.OnClickListener listener) { 105 | setOneOrTwoBtn(false); 106 | if (text != null) { 107 | btnNo.setText(text); 108 | } 109 | btnNo.setOnClickListener(v -> { 110 | dismiss(); 111 | listener.onClick(v); 112 | }); 113 | } 114 | 115 | /** 116 | * 设置按键类型 117 | * 118 | * @param one true 只有一个确认按键 ; false 显示 确认 和取消 按键 119 | */ 120 | private void setOneOrTwoBtn(boolean one) { 121 | if (one) { 122 | if (btnOneYes != null) 123 | btnOneYes.setVisibility(View.VISIBLE); 124 | if (llTwo != null) 125 | llTwo.setVisibility(View.GONE); 126 | } else { 127 | if (btnOneYes != null) 128 | btnOneYes.setVisibility(View.GONE); 129 | if (llTwo != null) 130 | llTwo.setVisibility(View.VISIBLE); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | import com.hqumath.androidmvvm.net.HandlerException; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.BufferedOutputStream; 7 | import java.io.Closeable; 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | 12 | import okhttp3.ResponseBody; 13 | 14 | /** 15 | * **************************************************************** 16 | * 文件名称: FileUtils 17 | * 作 者: Created by gyd 18 | * 创建时间: 2019/3/1 14:35 19 | * 文件描述: 文件管理 20 | * 注意事项: 21 | * 版权声明: 22 | * **************************************************************** 23 | */ 24 | public class FileUtil { 25 | 26 | /** 27 | * 根据url生成文件 28 | */ 29 | public static File getFileFromUrl(String url) { 30 | //获取文件名称类型 31 | String fileNameFromUrl1 = url.substring(url.lastIndexOf("/") + 1); 32 | //String fileName = fileNameFromUrl1.substring(0, fileNameFromUrl1.lastIndexOf("."));//文件名 33 | String fileStyle = fileNameFromUrl1.substring(fileNameFromUrl1.lastIndexOf(".") + 1);//文件类型 34 | //生成文件目录 35 | File fileDir = CommonUtil.getContext().getExternalFilesDir(fileStyle); 36 | if (!fileDir.exists()) 37 | fileDir.mkdirs(); 38 | String filePath = fileDir.getAbsolutePath() + "/" + fileNameFromUrl1; 39 | return new File(filePath); 40 | } 41 | 42 | /** 43 | * 根据app版本号生成文件 44 | */ 45 | public static File getFileFromVersionName(String version) { 46 | //获取文件名称类型 47 | String fileNameFromUrl1 = CommonUtil.getContext().getPackageName() + "-" + version; 48 | String fileStyle = "apk";//文件类型 49 | //生成文件目录 50 | File fileDir = CommonUtil.getContext().getExternalFilesDir(fileStyle); 51 | if (!fileDir.exists()) 52 | fileDir.mkdirs(); 53 | String filePath = fileDir.getAbsolutePath() + "/" + fileNameFromUrl1; 54 | return new File(filePath); 55 | } 56 | 57 | /** 58 | * 写文件 59 | */ 60 | public static void writeFile(ResponseBody responseBody, File file) { 61 | BufferedInputStream is = new BufferedInputStream(responseBody.byteStream()); 62 | BufferedOutputStream os = null; 63 | try { 64 | os = new BufferedOutputStream(new FileOutputStream(file)); 65 | byte data[] = new byte[1024 * 8]; 66 | int length = -1; 67 | while ((length = is.read(data)) != -1) { 68 | os.write(data, 0, length); 69 | } 70 | os.flush(); 71 | } catch (Exception e) { 72 | if (file != null && file.exists()) { 73 | file.deleteOnExit(); 74 | } 75 | e.printStackTrace(); 76 | throw new HandlerException.ResponseThrowable("文件下载错误", "-1"); 77 | } finally { 78 | closeStream(is); 79 | closeStream(os); 80 | } 81 | } 82 | 83 | private static void closeStream(Closeable closeable) { 84 | if (closeable != null) { 85 | try { 86 | closeable.close(); 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/PermissionUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.provider.Settings; 8 | import android.text.TextUtils; 9 | 10 | import androidx.appcompat.app.AlertDialog; 11 | 12 | import com.hqumath.androidmvvm.R; 13 | import com.yanzhenjie.permission.RequestExecutor; 14 | import com.yanzhenjie.permission.runtime.Permission; 15 | 16 | import java.io.File; 17 | import java.util.List; 18 | 19 | /** 20 | * **************************************************************** 21 | * 文件名称: PermissionUtils 22 | * 作 者: Created by gyd 23 | * 创建时间: 2019/2/27 15:12 24 | * 文件描述: 权限获取工具 25 | * 注意事项: 详见:https://github.com/yanzhenjie/AndPermission 26 | * 版权声明: 27 | * **************************************************************** 28 | */ 29 | public class PermissionUtil { 30 | public static final int REQUEST_CODE_SETTING = 1; 31 | 32 | //权限分组,尽量少用 33 | /*Permission.Group.CAMERA 相机 34 | Permission.Group.CONTACTS 通讯录 35 | Permission.Group.LOCATION 位置信息 36 | Permission.Group.CALENDAR 日历 37 | Permission.Group.MICROPHONE 麦克风 38 | Permission.Group.STORAGE 存储空间 39 | Permission.Group.PHONE 电话 40 | Permission.Group.SMS 短信*/ 41 | 42 | //使用例子 43 | /*AndPermission.with(activity) 44 | .runtime() 45 | .permission(Permission.Group.STORAGE) 46 | .onGranted((permissions) -> upload()) 47 | .onDenied((permissions) -> { 48 | if (AndPermission.hasAlwaysDeniedPermission(mContext, permissions)) { 49 | PermissionUtils.showSettingDialog(mContext, permissions);//自定义弹窗 去设置界面 50 | } 51 | }).start();*/ 52 | 53 | /** 54 | * 弹窗,去设置界面 55 | */ 56 | public static void showSettingDialog(Activity activity, final List permissions) { 57 | List permissionNames = Permission.transformText(activity, permissions); 58 | String message = activity.getString(R.string.permission_always_failed_message, TextUtils.join("\n", 59 | permissionNames)); 60 | new AlertDialog.Builder(activity).setCancelable(false) 61 | .setTitle(R.string.permission_failed_title) 62 | .setMessage(message) 63 | .setPositiveButton(R.string.permission_failed_setting, 64 | ((dialog, which) -> { 65 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 66 | intent.setData(Uri.fromParts("package", activity.getPackageName(), null)); 67 | activity.startActivity(intent); 68 | })) 69 | .setNegativeButton(R.string.permission_failed_cancel, null).show(); 70 | } 71 | 72 | /** 73 | * 弹窗,允许安装应用 74 | */ 75 | public static void showInstallDialog(Context context, File data, RequestExecutor executor) { 76 | new AlertDialog.Builder(context).setCancelable(false) 77 | .setTitle(R.string.permission_title) 78 | .setMessage(R.string.permission_install_failed_message) 79 | .setPositiveButton(R.string.permission_failed_setting, 80 | ((dialog, which) -> { 81 | executor.execute(); 82 | })) 83 | .setNegativeButton(R.string.permission_failed_cancel, ((dialog, which) -> { 84 | executor.cancel(); 85 | })).show(); 86 | } 87 | 88 | /** 89 | * 检查权限 90 | * @return 是否授权 91 | */ 92 | /*public static boolean check(Activity activity, String permission) { 93 | boolean isGranted = 94 | ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED; 95 | if (!isGranted) { 96 | ActivityCompat.requestPermissions(activity, new String[]{permission}, REQUEST_PERMISSION_CODE); 97 | } 98 | return isGranted; 99 | }*/ 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/SSLSocketClient.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | 4 | import java.security.SecureRandom; 5 | import java.security.cert.X509Certificate; 6 | 7 | import javax.net.ssl.HostnameVerifier; 8 | import javax.net.ssl.SSLContext; 9 | import javax.net.ssl.SSLSession; 10 | import javax.net.ssl.SSLSocketFactory; 11 | import javax.net.ssl.TrustManager; 12 | import javax.net.ssl.X509TrustManager; 13 | 14 | /** 15 | * 忽略https证书验证 16 | * 用法:OkHttpClient.Builder 17 | * builder.sslSocketFactory(SSLSocketClient.getSSLSocketFactory()); 18 | * builder.hostnameVerifier(SSLSocketClient.getHostnameVerifier()); 19 | */ 20 | 21 | public class SSLSocketClient { 22 | //获取这个SSLSocketFactory 23 | public static SSLSocketFactory getSSLSocketFactory() { 24 | try { 25 | SSLContext sslContext = SSLContext.getInstance("SSL"); 26 | sslContext.init(null, getTrustManager(), new SecureRandom()); 27 | return sslContext.getSocketFactory(); 28 | } catch (Exception e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | //获取TrustManager 34 | private static TrustManager[] getTrustManager() { 35 | TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { 36 | @Override 37 | public void checkClientTrusted(X509Certificate[] chain, String authType) { 38 | } 39 | 40 | @Override 41 | public void checkServerTrusted(X509Certificate[] chain, String authType) { 42 | } 43 | 44 | @Override 45 | public X509Certificate[] getAcceptedIssuers() { 46 | //okhttp3.0之前这里返回的是null 47 | return new X509Certificate[]{}; 48 | } 49 | }}; 50 | return trustAllCerts; 51 | } 52 | 53 | //获取HostnameVerifier 54 | public static HostnameVerifier getHostnameVerifier() { 55 | HostnameVerifier hostnameVerifier = new HostnameVerifier() { 56 | @Override 57 | public boolean verify(String s, SSLSession sslSession) { 58 | return true; 59 | } 60 | }; 61 | return hostnameVerifier; 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/SignatureUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | // 3 | 4 | import java.math.BigInteger; 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.util.Random; 8 | 9 | /** 10 | * 签名,认证 11 | */ 12 | public class SignatureUtil { 13 | 14 | /** 15 | * 获取随机数字、大小写字母 16 | * 17 | * @param len 长度 18 | * @return 19 | */ 20 | public static String getRandomNumLetter(int len) { 21 | char[] str = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 22 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 23 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; 24 | StringBuilder builder = new StringBuilder(); 25 | Random random = new Random(); 26 | for(int i = 0; i < len; i++) { 27 | builder.append(str[random.nextInt(62)]); 28 | } 29 | return builder.toString(); 30 | } 31 | 32 | /** 33 | * 计算MD5 34 | * @param input 35 | * @return 36 | */ 37 | public static String getMd5Value(String input) { 38 | if(input == null || input.length() == 0) { 39 | return ""; 40 | } 41 | try { 42 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 43 | md5.update(input.getBytes()); 44 | byte[] byteArray = md5.digest(); 45 | BigInteger bigInt = new BigInteger(1, byteArray); 46 | // 参数16表示16进制 47 | String result = bigInt.toString(16); 48 | // 不足32位高位补零 49 | while(result.length() < 32) { 50 | result = "0" + result; 51 | } 52 | return result; 53 | } catch (NoSuchAlgorithmException e) { 54 | e.printStackTrace(); 55 | } 56 | return ""; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/utils/ZxingUtil.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.utils; 2 | 3 | /** 4 | * Zxing二维码工具 5 | * 降级的引入,要求API14+ 6 | * implementation 'com.google.zxing:core:3.3.3' 7 | */ 8 | public class ZxingUtil { 9 | 10 | /** 11 | * @param url 12 | * @param qrWidth 300 13 | * @param qrHeight 300 14 | * @param deleteWhite 是否去除白边 15 | * @return 二维码 16 | */ 17 | /*public static Bitmap createQRCode(String url, int qrWidth, int qrHeight, boolean deleteWhite) { 18 | if (TextUtils.isEmpty(url)) return null; 19 | try { 20 | // 生成二维矩阵,编码时指定大小,不要生成了图片以后再进行缩放,这样会模糊导致识别失败 21 | Hashtable hints = new Hashtable<>(); 22 | hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); 23 | BitMatrix bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, qrWidth, qrHeight, hints); 24 | //去白边 25 | if (deleteWhite) bitMatrix = deleteWhite(bitMatrix); 26 | int width = bitMatrix.getWidth(); 27 | int height = bitMatrix.getHeight(); 28 | //二维矩阵转为一维像素数组 29 | int[] pixels = new int[width * height]; 30 | for (int y = 0; y < height; y++) { 31 | for (int x = 0; x < width; x++) { 32 | if (bitMatrix.get(x, y)) { 33 | pixels[y * width + x] = 0xff000000; 34 | } else { 35 | pixels[y * width + x] = 0xffffffff; 36 | } 37 | } 38 | } 39 | Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 40 | bitmap.setPixels(pixels, 0, width, 0, 0, width, height); 41 | return bitmap; 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | return null; 46 | }*/ 47 | 48 | /** 49 | * 去除白边 50 | * 51 | * @param matrix 52 | * @return 53 | */ 54 | /*private static BitMatrix deleteWhite(BitMatrix matrix) { 55 | int[] rec = matrix.getEnclosingRectangle(); 56 | int resWidth = rec[2] + 1; 57 | int resHeight = rec[3] + 1; 58 | 59 | BitMatrix resMatrix = new BitMatrix(resWidth, resHeight); 60 | resMatrix.clear(); 61 | for (int i = 0; i < resWidth; i++) { 62 | for (int j = 0; j < resHeight; j++) { 63 | if (matrix.get(i + rec[0], j + rec[1])) 64 | resMatrix.set(i, j); 65 | } 66 | } 67 | return resMatrix; 68 | }*/ 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/hqumath/androidmvvm/widget/DownloadingDialog.java: -------------------------------------------------------------------------------- 1 | package com.hqumath.androidmvvm.widget; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.appcompat.app.AppCompatDialog; 6 | 7 | import com.hqumath.androidmvvm.R; 8 | 9 | /** 10 | * 上传下载进度对话框 11 | */ 12 | public class DownloadingDialog extends AppCompatDialog { 13 | private DownloadingProgressBar mProgressBar; 14 | 15 | public DownloadingDialog(Context context) { 16 | super(context, R.style.AppDialogTheme); 17 | setContentView(R.layout.dialog_downloading); 18 | mProgressBar = findViewById(R.id.pb_downloading_content); 19 | setCancelable(true); 20 | } 21 | 22 | //20M以上的文件下载都会出现负数,已经下载的长度*100/总长度 23 | public void setProgress(long progressL, long maxProgressL) { 24 | int intMax; 25 | int intProgress; 26 | //int最大2147483647 27 | if (maxProgressL > 20000000L) { 28 | intMax = (int) (maxProgressL / 100); 29 | intProgress = (int) (progressL / 100); 30 | } else { 31 | intMax = (int) (maxProgressL); 32 | intProgress = (int) (progressL); 33 | } 34 | mProgressBar.setMax(intMax); 35 | mProgressBar.setProgress(intProgress); 36 | } 37 | 38 | @Override 39 | public void show() { 40 | super.show(); 41 | mProgressBar.setMax(100); 42 | mProgressBar.setProgress(0); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/res/anim/in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/out_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/push_bottom_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/push_bottom_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_button_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bottom_navigation_item_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dialog_common_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_code.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yadiq/AndroidMVVM/a58dab23492b71caf20ad88ad9e8f1b1fd688e4c/app/src/main/res/drawable/ic_custom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_empty.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_group_secondary.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_issues.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_link_secondary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mail.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mail_secondary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_person.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_star.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_trace.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yadiq/AndroidMVVM/a58dab23492b71caf20ad88ad9e8f1b1fd688e4c/app/src/main/res/drawable/ic_style.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_fileupdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 18 | 19 | 20 | 25 | 26 |