This commit is contained in:
coco
2026-07-03 16:23:31 +08:00
commit 7a4fb0e6ae
1979 changed files with 101570 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
# Built application files
*.apk
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
misc.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
+47
View File
@@ -0,0 +1,47 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.fastjetpack"
minSdk 29
targetSdk 32
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
viewBinding {
enabled = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(path: ':base')
implementation("com.github.DylanCaiCoding.ViewBindingKTX:viewbinding-ktx:2.0.2")
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
def nav_version = "2.3.5"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.aisier">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:name=".App"
android:icon="@mipmap/icon_android"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".ui.SecondActivity"
android:exported="false" />
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,18 @@
package com.aisier
import com.aisier.architecture.base.BaseApp
import com.ldlywt.colorful.ColorThemeConfig
import com.ldlywt.colorful.initColorful
class App : BaseApp() {
override fun onCreate() {
super.onCreate()
val colorThemeConfig = ColorThemeConfig(
useDarkTheme = true,
translucent = false,
customTheme = R.style.Theme_Red
)
initColorful(this, colorThemeConfig)
}
}
@@ -0,0 +1,12 @@
package com.aisier.bean
data class User(
val admin: Boolean?,
val chapterTops: List<Any>?,
val email: String?,
val icon: String?,
val id: Int?,
val nickname: String?,
val publicName: String?,
val username: String?
)
@@ -0,0 +1,23 @@
package com.aisier.bean
/**
* author : wutao
* time : 2020/01/01
* desc :
* version: 1.0
*/
class WxArticleBean {
/**
* id : 408
* name : 鸿洋
* order : 190000
* visible : 1
*/
var id = 0
var name: String? = null
var visible = 0
override fun toString(): String {
return "TestBean(id=$id, name=$name, visible=$visible)"
}
}
@@ -0,0 +1,26 @@
package com.aisier.net
import com.aisier.bean.User
import com.aisier.bean.WxArticleBean
import com.aisier.network.entity.ApiResponse
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
interface ApiService {
@GET("wxarticle/chapters/json")
suspend fun getWxArticle(): ApiResponse<List<WxArticleBean>>
@GET("abc/chapters/json")
suspend fun getWxArticleError(): ApiResponse<List<WxArticleBean>>
@FormUrlEncoded
@POST("user/login")
suspend fun login(@Field("username") userName: String, @Field("password") passWord: String): ApiResponse<User?>
companion object {
const val BASE_URL = "https://wanandroid.com/"
}
}
@@ -0,0 +1,12 @@
package com.aisier.net
import com.aisier.network.base.BaseRetrofitClient
import okhttp3.OkHttpClient
object RetrofitClient : BaseRetrofitClient() {
val service by lazy { getService(ApiService::class.java, ApiService.BASE_URL) }
override fun handleBuilder(builder: OkHttpClient.Builder) = Unit
}
@@ -0,0 +1,32 @@
package com.aisier.net
import com.aisier.bean.User
import com.aisier.bean.WxArticleBean
import com.aisier.network.base.BaseRepository
import com.aisier.network.entity.ApiResponse
class WxArticleRepository : BaseRepository() {
private val mService by lazy {
RetrofitClient.service
}
suspend fun fetchWxArticleFromNet(): ApiResponse<List<WxArticleBean>> {
return executeHttp {
mService.getWxArticle()
}
}
suspend fun fetchWxArticleError(): ApiResponse<List<WxArticleBean>> {
return executeHttp {
mService.getWxArticleError()
}
}
suspend fun login(username: String, password: String): ApiResponse<User?> {
return executeHttp {
mService.login(username, password)
}
}
}
@@ -0,0 +1,6 @@
package com.aisier.ui
import com.aisier.R
import com.aisier.architecture.base.BaseActivity
class MainActivity : BaseActivity(R.layout.activity_home)
@@ -0,0 +1,20 @@
package com.aisier.ui
import android.os.Bundle
import android.view.View
import com.aisier.R
import com.aisier.architecture.base.BaseActivity
import com.ldlywt.colorful.ColorTheme
import com.ldlywt.colorful.ThemeStyle
class SecondActivity : BaseActivity(R.layout.activity_second) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findViewById<View>(R.id.bt_change_theme).setOnClickListener {
ColorTheme().edit()
.setDarkTheme(false)
.setCustomThemeOverride(ThemeStyle.values().random().res)
.applyAndRestart(this)
}
}
}
@@ -0,0 +1,26 @@
package com.aisier.ui.fragment
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.navigation.findNavController
import com.aisier.R
import com.aisier.architecture.base.BaseFragment
import com.aisier.ui.SecondActivity
class MainFragment : BaseFragment(R.layout.fragment_main) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<View>(R.id.bt_api).setOnClickListener {
view.findNavController().navigate(R.id.netListFragment)
}
view.findViewById<Button>(R.id.bt_save_state).setOnClickListener {
view.findNavController().navigate(R.id.savedStateFragment)
}
view.findViewById<Button>(R.id.bt_change_theme).setOnClickListener {
requireActivity().startActivity(Intent(requireActivity(), SecondActivity::class.java))
}
}
}
@@ -0,0 +1,113 @@
package com.aisier.ui.fragment
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.asLiveData
import com.aisier.R
import com.aisier.architecture.base.BaseFragment
import com.aisier.architecture.util.collectIn
import com.aisier.architecture.util.launchFlow
import com.aisier.architecture.util.launchWithLoading
import com.aisier.architecture.util.launchWithLoadingAndCollect
import com.aisier.bean.WxArticleBean
import com.aisier.databinding.FragmentNetListBinding
import com.aisier.network.observeState
import com.aisier.network.toast
import com.aisier.vm.ApiViewModel
import com.dylanc.viewbinding.binding
/**
* dev 分支去掉LiveData,使用Flow
*/
class NetListFragment : BaseFragment(R.layout.fragment_net_list) {
// navigation情况下不能用Activity的ViewModel
private val mViewModel by viewModels<ApiViewModel>()
private val mBinding: FragmentNetListBinding by binding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initData()
initObserver()
}
private fun initObserver() {
mViewModel.uiState.collectIn(this, Lifecycle.State.STARTED) {
onSuccess = { result: List<WxArticleBean>? ->
showNetErrorPic(false)
mBinding.tvContent.text = result.toString()
}
onComplete = { Log.i("NetListFragment", ": onComplete") }
onFailed = { code, msg -> toast("errorCode: $code errorMsg: $msg") }
onError = { showNetErrorPic(true) }
}
}
private fun showNetErrorPic(isShowError: Boolean) {
mBinding.tvContent.isGone = isShowError
mBinding.ivContent.isVisible = isShowError
}
private fun initData() {
mBinding.btnNet.setOnClickListener { requestNet() }
mBinding.btnNetError.setOnClickListener {
showNetErrorPic(false)
requestNetError()
}
mBinding.btLogin.setOnClickListener {
showNetErrorPic(false)
login()
}
}
private fun requestNet() = launchWithLoading(mViewModel::requestNet)
private fun requestNetError() = launchWithLoading(mViewModel::requestNetError)
/**
* 链式调用,返回结果的处理都在一起,viewmodel中不需要创建一个livedata对象
* 适用于不需要监听数据变化的场景
* 屏幕旋转,Activity销毁重建,数据会消失
*/
private fun login() {
launchWithLoadingAndCollect({
mViewModel.login("FastJetpack", "FastJetpack")
}) {
onSuccess = {
mBinding.tvContent.text = it.toString()
}
onFailed = { errorCode, errorMsg ->
toast("errorCode: $errorCode errorMsg: $errorMsg")
}
}
}
/**
* 将Flow转变为LiveData
*/
private fun loginAsLiveData() {
val loginLiveData =
launchFlow(requestBlock = {
mViewModel.login(
"FastJetpack",
"FastJetpack11"
)
}).asLiveData()
loginLiveData.observeState(this) {
onSuccess = { mBinding.tvContent.text = it.toString() }
onFailed =
{ errorCode, errorMsg -> toast("errorCode: $errorCode errorMsg: $errorMsg") }
}
}
}
@@ -0,0 +1,33 @@
package com.aisier.ui.fragment
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.aisier.R
import com.aisier.vm.SavedStateViewModel
class SavedStateFragment : Fragment(R.layout.fragment_saved_state) {
private val stateViewModel by viewModels<SavedStateViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.i("SavedStateActivity--> ", "SavedStateViewModel: $stateViewModel")
Log.i("SavedStateActivity--> ", "userName: ${stateViewModel.userName}")
val value: String = stateViewModel.inputLiveData.value.toString()
Log.i("SavedStateActivity--> ", "input text: ${value}")
val submit = view.findViewById<Button>(R.id.submit)
val editText = view.findViewById<EditText>(R.id.edit_text)
submit.setOnClickListener {
stateViewModel.userName = "Hello world"
val inputText: String = editText.toString()
stateViewModel.inputLiveData.value = inputText
}
}
}
@@ -0,0 +1,44 @@
package com.aisier.vm
import com.aisier.architecture.base.BaseViewModel
import com.aisier.bean.User
import com.aisier.bean.WxArticleBean
import com.aisier.net.WxArticleRepository
import com.aisier.network.entity.ApiResponse
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* <pre>
* @author : wutao
* time : 2019/08/17
* desc :
* version: 1.0
</pre> *
*/
class ApiViewModel : BaseViewModel() {
private val repository by lazy { WxArticleRepository() }
// 使用StateFlow 替代livedata
// val wxArticleLiveData = StateMutableLiveData<List<WxArticleBean>>()
private val _uiState = MutableStateFlow<ApiResponse<List<WxArticleBean>>>(ApiResponse())
val uiState: StateFlow<ApiResponse<List<WxArticleBean>>> = _uiState.asStateFlow()
suspend fun requestNet() {
_uiState.value = repository.fetchWxArticleFromNet()
}
suspend fun requestNetError() {
_uiState.value = repository.fetchWxArticleError()
}
/**
* 场景:不需要监听数据变化
*/
suspend fun login(username: String, password: String): ApiResponse<User?> {
return repository.login(username, password)
}
}
@@ -0,0 +1,19 @@
package com.aisier.vm
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
companion object {
const val SAVE_STATE_KEY_STRING = "user_name"
const val SAVE_STATE_KEY_LIVE_DATE = "input_livedata"
}
var userName: String?
get() = savedStateHandle.get(SAVE_STATE_KEY_STRING)
set(value) = savedStateHandle.set(SAVE_STATE_KEY_STRING, value)
val inputLiveData = savedStateHandle.getLiveData<String>(SAVE_STATE_KEY_LIVE_DATE)
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.SecondActivity">
<Button
android:id="@+id/bt_change_theme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="改变主题" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="占位" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<SeekBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:progress="50" />
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/app_name" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TimePicker
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.MainFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<Button
android:id="@+id/bt_api"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="网络请求封装" />
<Button
android:id="@+id/bt_save_state"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="数据恢复" />
<Button
android:id="@+id/bt_change_theme"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="随机改变主题" />
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.NetListFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/bt_login"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:text="登录" />
<Button
android:id="@+id/btn_net"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="请求网络" />
<Button
android:id="@+id/btn_net_error"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="请求网络错误" />
</LinearLayout>
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="10dp"
android:layout_weight="1"
android:text="网络请求结果:" />
<ImageView
android:id="@+id/iv_content"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_margin="10dp"
android:src="@mipmap/icon_net_error"
android:visibility="gone" />
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入名字" />
<Button
android:id="@+id/submit"
android:text="提交"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.aisier.ui.fragment.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_netListFragment"
app:destination="@id/netListFragment" />
<action
android:id="@+id/action_mainFragment_to_savedStateFragment"
app:destination="@id/savedStateFragment" />
</fragment>
<fragment
android:id="@+id/netListFragment"
android:name="com.aisier.ui.fragment.NetListFragment"
android:label="fragment_net_list"
tools:layout="@layout/fragment_net_list" />
<fragment
android:id="@+id/savedStateFragment"
android:name="com.aisier.ui.fragment.SavedStateFragment"
android:label="fragment_saved_state"
tools:layout="@layout/fragment_saved_state" />
</navigation>
@@ -0,0 +1,5 @@
<resources>
<string name="app_name">FastJetpack</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>