├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── gradle.xml
├── kotlinc.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── ldlywt
│ │ └── commoncode
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── ldlywt
│ │ │ └── commoncode
│ │ │ ├── App.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── activity
│ │ │ └── LiveDataTestActivity.kt
│ │ │ ├── livedata
│ │ │ ├── NetworkWatchLiveData.kt
│ │ │ ├── RequestPermissionLiveData.kt
│ │ │ ├── TakePhotoLiveData.kt
│ │ │ └── TimerGlobalLiveData.kt
│ │ │ ├── location
│ │ │ ├── FusedLocationHelper.kt
│ │ │ ├── LocationHelper.kt
│ │ │ ├── LocationHelperV2.kt
│ │ │ ├── LocationPermissionUtils.kt
│ │ │ └── NetWorkLocationHelper.kt
│ │ │ ├── permission
│ │ │ └── PermissionKtx.kt
│ │ │ └── view
│ │ │ └── LifecycleView.kt
│ └── res
│ │ ├── color
│ │ ├── color_md_contained_btn_background.xml
│ │ ├── color_md_contained_btn_stroke.xml
│ │ ├── color_md_contained_btn_text.xml
│ │ ├── color_md_dashed_btn_text.xml
│ │ ├── color_md_outlined_btn_background.xml
│ │ ├── color_md_outlined_btn_stroke.xml
│ │ ├── color_md_outlined_btn_text.xml
│ │ ├── color_md_text_btn_background.xml
│ │ └── color_md_text_btn_text.xml
│ │ ├── drawable
│ │ └── bg_button_dash_selector.xml
│ │ ├── layout
│ │ ├── activity_live_data_test.xml
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── ldlywt
│ └── commoncode
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ktx
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── ldlywt
│ │ └── ktx
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── ldlywt
│ │ └── ktx
│ │ ├── ActivityKtx.kt
│ │ ├── Cursor.kt
│ │ ├── EditText.kt
│ │ ├── File.kt
│ │ ├── FlowKtx.kt
│ │ ├── Network.kt
│ │ ├── StringKtx.kt
│ │ ├── Version.kt
│ │ ├── ViewKtx.kt
│ │ ├── clipboard.kt
│ │ ├── dp.kt
│ │ ├── hidekeyboard.kt
│ │ ├── snackbar.kt
│ │ └── toast.kt
│ └── test
│ └── java
│ └── com
│ └── ldlywt
│ └── ktx
│ └── ExampleUnitTest.kt
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android Common Generic Code Organizer
2 |
3 | ## Outline
4 |
5 | [](https://imgtu.com/i/o5MSdP)
6 |
7 | ## MaterialDesign Button Styles
8 |
9 | [](https://imgtu.com/i/o5Qe6H)
10 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdk 31
8 |
9 | defaultConfig {
10 | applicationId "com.ldlywt.commoncode"
11 | minSdk 24
12 | targetSdk 31
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = '1.8'
31 | }
32 | viewBinding {
33 | enabled = true
34 | }
35 | }
36 |
37 | dependencies {
38 |
39 | implementation 'androidx.core:core-ktx:1.6.0'
40 | implementation 'androidx.appcompat:appcompat:1.3.1'
41 | implementation 'com.google.android.material:material:1.4.0'
42 | implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
43 | implementation project(path: ':ktx')
44 | testImplementation 'junit:junit:4.+'
45 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
47 | implementation 'com.google.android.gms:play-services-location:18.0.0'
48 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
49 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
50 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-rc01'
51 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ldlywt/commoncode/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ldlywt.commoncode", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/App.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode
2 |
3 | import android.app.Application
4 |
5 | val applicationContext = App.instance
6 |
7 | class App : Application() {
8 |
9 | override fun onCreate() {
10 | super.onCreate()
11 | instance = this
12 | }
13 |
14 | companion object {
15 | lateinit var instance: App
16 | private set
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import androidx.appcompat.app.AppCompatActivity
6 | import android.os.Bundle
7 | import androidx.activity.result.ActivityResult
8 | import androidx.activity.result.ActivityResultLauncher
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import com.ldlywt.commoncode.activity.LiveDataTestActivity
11 | import com.ldlywt.commoncode.databinding.ActivityMainBinding
12 | import com.ldlywt.commoncode.ktx.toast
13 | import com.ldlywt.commoncode.view.LifecycleView
14 |
15 | class MainActivity : AppCompatActivity(R.layout.activity_main) {
16 |
17 | private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
18 |
19 | private val activityResultLauncher: ActivityResultLauncher =
20 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
21 | if (activityResult.resultCode == Activity.RESULT_OK) {
22 | toast(activityResult.data?.getStringExtra("key") ?: "")
23 | }
24 | }
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | setContentView(mBinding.root)
29 | mBinding.btLiveData.setOnClickListener {
30 | activityResultLauncher.launch(Intent(this, LiveDataTestActivity::class.java))
31 | }
32 | mBinding.root.addView(LifecycleView(this, lifecycleOwner = this))
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/activity/LiveDataTestActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.activity
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.util.Log
8 | import androidx.activity.result.ActivityResultLauncher
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.appcompat.app.AppCompatActivity
11 | import androidx.lifecycle.Lifecycle
12 | import androidx.lifecycle.lifecycleScope
13 | import com.ldlywt.commoncode.R
14 | import com.ldlywt.commoncode.databinding.ActivityLiveDataTestBinding
15 | import com.ldlywt.ktx.launchAndCollectIn
16 | import com.ldlywt.commoncode.ktx.toast
17 | import com.ldlywt.commoncode.livedata.RequestPermissionLiveData
18 | import com.ldlywt.commoncode.livedata.TakePhotoLiveData
19 | import com.ldlywt.commoncode.livedata.TimerGlobalLiveData
20 | import com.ldlywt.commoncode.location.LocationHelperV2
21 | import com.ldlywt.commoncode.location.LocationPermissionUtils
22 | import com.ldlywt.commoncode.location.NetWorkLocationHelper
23 | import kotlinx.coroutines.launch
24 |
25 | class LiveDataTestActivity : AppCompatActivity(R.layout.activity_live_data_test) {
26 |
27 | private val mBinding by lazy { ActivityLiveDataTestBinding.inflate(layoutInflater) }
28 |
29 | private var takePhotoLiveData: TakePhotoLiveData =
30 | TakePhotoLiveData(activityResultRegistry, "key")
31 |
32 | private var requestPermissionLiveData = RequestPermissionLiveData(activityResultRegistry, "key")
33 |
34 | private val requestLocationPermissionLauncher: ActivityResultLauncher =
35 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { result: Boolean ->
36 | toast("request permission $result")
37 | if (result) {
38 | lifecycleScope.launch {
39 | // val location = NetWorkLocationHelper().getNetLocation(this@LiveDataTestActivity)
40 | // Log.i("wutao--> ", "location:: $location")
41 | // NetWorkLocationHelper(this@LiveDataTestActivity, lifecycleScope)
42 | // .getNetLocationFlow()
43 | // .buffer(Channel.CONFLATED)
44 | // .debounce(300)
45 | // .collect { location ->
46 | // Log.i("wutao--> ", "location:: $location")
47 | // }
48 |
49 | val location = LocationHelperV2(this@LiveDataTestActivity, lifecycleScope).getLocation()
50 | Log.i("wutao--> ", "val location = : $location")
51 | }
52 | }
53 | }
54 |
55 | override fun onCreate(savedInstanceState: Bundle?) {
56 | super.onCreate(savedInstanceState)
57 | setContentView(mBinding.root)
58 | init()
59 | requestLocationWhenOnStart()
60 | }
61 |
62 | private fun requestLocationWhenOnStart() {
63 | if (LocationPermissionUtils.isLocationPermissionGranted(this)) {
64 | NetWorkLocationHelper(this, lifecycleScope)
65 | .getNetLocationFlow()
66 | .launchAndCollectIn(this, Lifecycle.State.RESUMED) {
67 | Log.i("wutao--> ", "New Location : $it")
68 | }
69 | }
70 | }
71 |
72 | private fun init() {
73 |
74 | takePhotoLiveData.observe(this) { bitmap -> mBinding.imageView.setImageBitmap(bitmap) }
75 |
76 | mBinding.btTakePhoto.setOnClickListener { takePhotoLiveData.takePhoto() }
77 |
78 | mBinding.btStopTimer.setOnClickListener {
79 | //启动全局计算器
80 | //TimerGlobalLiveData.get().startTimer()
81 | TimerGlobalLiveData.get().cancelTimer()
82 | }
83 |
84 | TimerGlobalLiveData.get().observe(this) { Log.i("LiveDataTestActivity", "GlobalTimer value: == $it") }
85 |
86 | mBinding.btRequestPermission.setOnClickListener { requestLocationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) }
87 |
88 | mBinding.btRequestPermissionV2.setOnClickListener { requestPermissionLiveData.requestPermission(Manifest.permission.RECORD_AUDIO) }
89 |
90 | requestPermissionLiveData.observe(this) { toast("权限RECORD_AUDIO请求结果 $it") }
91 |
92 | mBinding.btBack.setOnClickListener {
93 | setResult(Activity.RESULT_OK, Intent().putExtra("key", "返回消息"))
94 | finish()
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/livedata/NetworkWatchLiveData.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.livedata
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.BroadcastReceiver
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.IntentFilter
8 | import android.net.ConnectivityManager
9 | import android.net.NetworkInfo
10 | import androidx.annotation.MainThread
11 | import androidx.lifecycle.LiveData
12 |
13 | class NetworkWatchLiveData(context: Context) : LiveData() {
14 | private val mContext = context.applicationContext
15 | private val mNetworkReceiver: NetworkReceiver = NetworkReceiver()
16 | private val mIntentFilter: IntentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
17 |
18 | override fun onActive() {
19 | mContext.registerReceiver(mNetworkReceiver, mIntentFilter)
20 | }
21 |
22 | override fun onInactive() = mContext.unregisterReceiver(mNetworkReceiver)
23 |
24 | private class NetworkReceiver : BroadcastReceiver() {
25 | @SuppressLint("MissingPermission")
26 | override fun onReceive(context: Context, intent: Intent) {
27 | val manager =
28 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
29 | val activeNetwork = manager.activeNetworkInfo
30 | sInstance.postValue(activeNetwork)
31 | }
32 | }
33 |
34 | companion object {
35 |
36 | private lateinit var sInstance: NetworkWatchLiveData
37 |
38 | @MainThread
39 | fun get(context: Context): NetworkWatchLiveData {
40 | sInstance = if (Companion::sInstance.isInitialized) sInstance else NetworkWatchLiveData(context)
41 | return sInstance
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/livedata/RequestPermissionLiveData.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.livedata
2 |
3 | import androidx.activity.result.ActivityResultLauncher
4 | import androidx.activity.result.ActivityResultRegistry
5 | import androidx.activity.result.contract.ActivityResultContracts
6 | import androidx.lifecycle.LiveData
7 |
8 | class RequestPermissionLiveData(
9 | private val registry: ActivityResultRegistry,
10 | private val key: String
11 | ) : LiveData() {
12 |
13 | private lateinit var requestPermissionLauncher: ActivityResultLauncher
14 |
15 | override fun onActive() {
16 | requestPermissionLauncher =
17 | registry.register(key, ActivityResultContracts.RequestPermission()) { result ->
18 | value = result
19 | }
20 | }
21 |
22 | override fun onInactive() = requestPermissionLauncher.unregister()
23 |
24 | fun requestPermission(permission: String) {
25 | requestPermissionLauncher.launch(permission)
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/livedata/TakePhotoLiveData.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.livedata
2 |
3 | import android.graphics.Bitmap
4 | import androidx.activity.result.ActivityResultLauncher
5 | import androidx.activity.result.ActivityResultRegistry
6 | import androidx.activity.result.contract.ActivityResultContracts
7 | import androidx.lifecycle.LiveData
8 |
9 | class TakePhotoLiveData(private val registry: ActivityResultRegistry, private val key: String) :
10 | LiveData() {
11 |
12 | private lateinit var takePhotoLauncher: ActivityResultLauncher
13 |
14 | override fun onActive() {
15 | takePhotoLauncher =
16 | registry.register(key, ActivityResultContracts.TakePicturePreview()) { result ->
17 | value = result
18 | }
19 | }
20 |
21 | override fun onInactive() = takePhotoLauncher.unregister()
22 |
23 | fun takePhoto() = takePhotoLauncher.launch(null)
24 |
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/livedata/TimerGlobalLiveData.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.livedata
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import androidx.annotation.MainThread
6 | import androidx.lifecycle.LiveData
7 |
8 | class TimerGlobalLiveData : LiveData() {
9 |
10 | private val handler: Handler = Handler(Looper.getMainLooper())
11 |
12 | private val timerRunnable = object : Runnable {
13 | override fun run() {
14 | postValue(count++)
15 | handler.postDelayed(this, 1000)
16 | }
17 | }
18 |
19 | fun startTimer() {
20 | count = 0
21 | handler.postDelayed(timerRunnable, 1000)
22 | }
23 |
24 | fun cancelTimer() {
25 | handler.removeCallbacks(timerRunnable)
26 | }
27 |
28 | companion object {
29 | private lateinit var sInstance: TimerGlobalLiveData
30 |
31 | private var count = 0
32 |
33 | @MainThread
34 | fun get(): TimerGlobalLiveData {
35 | sInstance = if (Companion::sInstance.isInitialized) sInstance else TimerGlobalLiveData()
36 | return sInstance
37 | }
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/location/FusedLocationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.location
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.location.Location
6 | import android.os.Looper
7 | import android.util.Log
8 | import com.google.android.gms.location.LocationCallback
9 | import com.google.android.gms.location.LocationRequest
10 | import com.google.android.gms.location.LocationResult
11 | import com.google.android.gms.location.LocationServices
12 | import kotlinx.coroutines.CoroutineScope
13 | import kotlinx.coroutines.ExperimentalCoroutinesApi
14 | import kotlinx.coroutines.channels.awaitClose
15 | import kotlinx.coroutines.flow.*
16 |
17 | class FusedLocationHelper constructor(context: Context, externalScope: CoroutineScope) {
18 |
19 | private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
20 | private val locationRequest = createLocationRequest()
21 |
22 | private val TAG = "SharedLocationManager"
23 |
24 | private fun createLocationRequest() = LocationRequest.create().apply {
25 | interval = 5000
26 | fastestInterval = 2000
27 | numUpdates = 1
28 | priority = LocationRequest.PRIORITY_HIGH_ACCURACY
29 | }
30 |
31 | @ExperimentalCoroutinesApi
32 | @SuppressLint("MissingPermission")
33 | private val _locationUpdates: SharedFlow = callbackFlow {
34 | val callback = object : LocationCallback() {
35 | override fun onLocationResult(result: LocationResult?) {
36 | result ?: return
37 | Log.d(TAG, "New location: ${result.lastLocation}")
38 | offer(result.lastLocation)
39 | }
40 |
41 | }
42 | Log.d(TAG, "Starting location updates")
43 |
44 | fusedLocationClient.requestLocationUpdates(
45 | locationRequest,
46 | callback,
47 | Looper.getMainLooper()
48 | ).addOnFailureListener { e ->
49 | close(e)
50 | }
51 |
52 | awaitClose {
53 | Log.d(TAG, "Stopping location updates")
54 | fusedLocationClient.removeLocationUpdates(callback)
55 | }
56 | }.shareIn(
57 | externalScope,
58 | replay = 0,
59 | started = SharingStarted.WhileSubscribed()
60 | )
61 |
62 | @ExperimentalCoroutinesApi
63 | fun locationFlow(): Flow {
64 | return _locationUpdates
65 | }
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/location/LocationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.location
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.location.Location
6 | import android.location.LocationListener
7 | import android.location.LocationManager
8 | import android.os.Build
9 | import android.os.Looper
10 | import kotlinx.coroutines.delay
11 |
12 | object LocationHelper {
13 |
14 | @SuppressLint("MissingPermission")
15 | suspend fun getLocation(context: Context, timeout: Long, callback: (location: Location?) -> Unit) {
16 | val locationManager: LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
17 |
18 | try {
19 | var bestLocation: Location? = null
20 | var hasSendResult = false
21 |
22 | if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
23 | val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
24 | if (isBetterLocation(location, bestLocation)) {
25 | bestLocation = location
26 | }
27 | }
28 | if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
29 | val location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
30 | if (isBetterLocation(location, bestLocation)) {
31 | bestLocation = location
32 | }
33 | }
34 |
35 | var gpsListener: LocationListener?
36 | var networkListener: LocationListener? = null
37 | object : LocationListener {
38 | override fun onLocationChanged(location: Location) {
39 | if (isBetterLocation(location, bestLocation)) {
40 | bestLocation = location
41 | }
42 | locationManager.removeUpdates(this)
43 | gpsListener = null
44 | if (bestLocation != null) {
45 | callback(bestLocation)
46 | hasSendResult = true
47 | networkListener?.let {
48 | locationManager.removeUpdates(it)
49 | }
50 | }
51 | }
52 | }.also { gpsListener = it }
53 |
54 | networkListener = object : LocationListener {
55 | override fun onLocationChanged(location: Location) {
56 | if (isBetterLocation(location, bestLocation)) {
57 | bestLocation = location
58 | }
59 | locationManager.removeUpdates(this)
60 | networkListener = null
61 | }
62 | }
63 |
64 | if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
65 | gpsListener?.let {
66 | requestSingleUpdate(context, locationManager, LocationManager.GPS_PROVIDER, it) {
67 | callback(bestLocation)
68 | hasSendResult = true
69 | }
70 | }
71 | }
72 |
73 | if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
74 | networkListener?.let {
75 | requestSingleUpdate(context, locationManager, LocationManager.NETWORK_PROVIDER, it) {
76 | callback(bestLocation)
77 | hasSendResult = true
78 | }
79 | }
80 | }
81 |
82 | delay(timeout)
83 |
84 | if (!hasSendResult) callback(bestLocation)
85 |
86 | gpsListener?.let { locationManager.removeUpdates(it) }
87 |
88 | networkListener?.let { locationManager.removeUpdates(it) }
89 | } catch (t: SecurityException) {
90 | callback(null)
91 | }
92 | }
93 |
94 | @SuppressLint("MissingPermission")
95 | private fun requestSingleUpdate(context: Context, locationManager: LocationManager, provider: String, locationListener: LocationListener, resultCallback: ((location: Location) -> Unit)? = null) {
96 | if (!locationManager.isProviderEnabled(provider)) {
97 | return
98 | }
99 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
100 | locationManager.getCurrentLocation(provider, null, context.mainExecutor) { location ->
101 | location?.let {
102 | resultCallback?.invoke(it)
103 | }
104 |
105 | }
106 | } else {
107 | locationManager.requestSingleUpdate(provider, locationListener, Looper.getMainLooper())
108 | }
109 | }
110 |
111 |
112 | private fun isBetterLocation(location: Location?, currentBestLocation: Location?): Boolean {
113 | if (location == null) {
114 | return false
115 | }
116 | if (currentBestLocation == null) {
117 | // A new location is always better than no location
118 | return true
119 | }
120 |
121 | val TWO_MINUTES = 1000 * 60 * 2
122 |
123 | // Check whether the new location fix is newer or older
124 | val timeDelta = location.time - currentBestLocation.time
125 | val isSignificantlyNewer: Boolean = timeDelta > TWO_MINUTES
126 | val isSignificantlyOlder: Boolean = timeDelta < -TWO_MINUTES
127 | val isNewer = timeDelta > 0
128 |
129 | // If it's been more than two minutes since the current location, use
130 | // the new location
131 | // because the user has likely moved
132 | if (isSignificantlyNewer) {
133 | return true
134 | // If the new location is more than two minutes older, it must be
135 | // worse
136 | } else if (isSignificantlyOlder) {
137 | return false
138 | }
139 |
140 | // Check whether the new location fix is more or less accurate
141 | val accuracyDelta = (location.accuracy - currentBestLocation.accuracy).toInt()
142 | val isLessAccurate = accuracyDelta > 0
143 | val isMoreAccurate = accuracyDelta < 0
144 | val isSignificantlyLessAccurate = accuracyDelta > 200
145 |
146 | // Check if the old and new location are from the same provider
147 | val isFromSameProvider = location.provider == currentBestLocation.provider
148 |
149 | // Not significantly newer or older, so check for Accuracy
150 | if (isMoreAccurate) {
151 | // If more accurate return true
152 | return true
153 | } else if (isNewer && !isLessAccurate) {
154 | // Same accuracy but newer, return true
155 | return true
156 | } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
157 | // Accuracy is less (not much though) but is new, so if from same
158 | // provider return true
159 | return true
160 | }
161 | return false
162 | }
163 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/location/LocationHelperV2.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.location
2 |
3 | import android.content.Context
4 | import android.location.Location
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlinx.coroutines.flow.collect
7 | import kotlinx.coroutines.withTimeoutOrNull
8 |
9 | /**
10 | * 先获取网络定位 ,如果当前在室内,拿不到 gps 的情况下,设置2s 超时,如果拿的到 gps 位置,就返回最新的 gps 位置
11 | */
12 | class LocationHelperV2(val context: Context, val externalScope: CoroutineScope) {
13 |
14 | suspend fun getLocation(): Location? {
15 | var location: Location? =
16 | NetWorkLocationHelper(context, externalScope).getNetLocation(context)
17 | withTimeoutOrNull(2000) {
18 | FusedLocationHelper(context, externalScope)
19 | .locationFlow()
20 | .collect {
21 | location = it
22 | }
23 | }
24 | return location
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/location/LocationPermissionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.location
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.Context
6 | import android.content.pm.PackageManager
7 | import android.location.LocationManager
8 | import androidx.core.content.ContextCompat
9 |
10 | object LocationPermissionUtils {
11 |
12 | fun isLocationPermissionGranted(context: Context): Boolean {
13 | return (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) === PackageManager.PERMISSION_GRANTED
14 | || ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) === PackageManager.PERMISSION_GRANTED)
15 | }
16 |
17 | fun isLocationProviderEnabled(context: Context): Boolean {
18 | val locationManager: LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
19 | return (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
20 | || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/location/NetWorkLocationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.location
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.location.Location
6 | import android.location.LocationListener
7 | import android.location.LocationManager
8 | import android.os.Build
9 | import android.os.Looper
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.ExperimentalCoroutinesApi
12 | import kotlinx.coroutines.channels.awaitClose
13 | import kotlinx.coroutines.flow.Flow
14 | import kotlinx.coroutines.flow.SharingStarted
15 | import kotlinx.coroutines.flow.callbackFlow
16 | import kotlinx.coroutines.flow.shareIn
17 | import kotlinx.coroutines.suspendCancellableCoroutine
18 | import kotlin.coroutines.resume
19 |
20 | class NetWorkLocationHelper(context: Context, externalScope: CoroutineScope) {
21 |
22 | private val locationManager: LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
23 |
24 | @SuppressLint("MissingPermission")
25 | suspend fun getNetLocation(context: Context, callback: (location: Location) -> Unit) {
26 |
27 | if (!locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
28 | return
29 | }
30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
31 | locationManager.getCurrentLocation(LocationManager.NETWORK_PROVIDER, null, context.mainExecutor) { location ->
32 | callback.invoke(location)
33 | }
34 | } else {
35 | locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, { location ->
36 | callback.invoke(location)
37 | }, null)
38 | }
39 | }
40 |
41 |
42 | @SuppressLint("MissingPermission")
43 | suspend fun getNetLocation(context: Context): Location? = suspendCancellableCoroutine { continuation ->
44 | if (!locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
45 | continuation.resume(null)
46 | }
47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
48 | locationManager.getCurrentLocation(LocationManager.NETWORK_PROVIDER, null, context.mainExecutor) { location ->
49 | continuation.resume(location)
50 | }
51 | } else {
52 | locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, { location ->
53 | continuation.resume(location)
54 | }, Looper.getMainLooper())
55 | }
56 | }
57 |
58 | /**
59 | * 注意!不要在每个函数调用时创建新的实例
60 | * 切勿在调用某个函数调用返回时,使用 shareIn 或 stateIn 创建新的数据流。
61 | * 这样会在每次函数调用时创建一个新的 SharedFlow 或 StateFlow,而它们将会一直保持在内存中,直到作用域被取消或者在没有任何引用时被垃圾回收。
62 | */
63 | @ExperimentalCoroutinesApi
64 | fun getNetLocationFlow(): Flow {
65 | return _locationUpdates
66 | }
67 |
68 | /**
69 | * 返回Flow流封装的,支持操作符,支持背压
70 | */
71 | @ExperimentalCoroutinesApi
72 | @SuppressLint("MissingPermission")
73 | private val _locationUpdates = callbackFlow {
74 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
75 | locationManager.getCurrentLocation(LocationManager.NETWORK_PROVIDER, null, context.mainExecutor) { location ->
76 | offer(location)
77 | }
78 | awaitClose()
79 | } else {
80 | val locationListener = LocationListener { location -> offer(location) }
81 | locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, locationListener, Looper.getMainLooper())
82 | awaitClose {
83 | locationManager.removeUpdates(locationListener)
84 | }
85 | }
86 | }.shareIn(
87 | externalScope,
88 | replay = 0,
89 | started = SharingStarted.WhileSubscribed()
90 | )
91 |
92 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/permission/PermissionKtx.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.permission
2 |
3 | import androidx.activity.result.contract.ActivityResultContracts
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.core.app.ActivityCompat
6 | import androidx.fragment.app.Fragment
7 |
8 | inline fun Fragment.requestPermission(
9 | permission: String,
10 | crossinline granted: (permission: String) -> Unit = {},
11 | crossinline denied: (permission: String) -> Unit = {},
12 | crossinline explained: (permission: String) -> Unit = {}
13 |
14 | ) {
15 |
16 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { result: Boolean ->
17 | when {
18 | result -> granted.invoke(permission)
19 | shouldShowRequestPermissionRationale(permission) -> denied.invoke(permission)
20 | else -> explained.invoke(permission)
21 | }
22 | }.launch(permission)
23 | }
24 |
25 | inline fun AppCompatActivity.requestPermission(
26 | permission: String,
27 | crossinline granted: (permission: String) -> Unit = {},
28 | crossinline denied: (permission: String) -> Unit = {},
29 | crossinline explained: (permission: String) -> Unit = {}
30 | ) {
31 |
32 |
33 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
34 | when {
35 | result -> granted.invoke(permission)
36 | ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> denied.invoke(
37 | permission
38 | )
39 | else -> explained.invoke(permission)
40 | }
41 | }.launch(permission)
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ldlywt/commoncode/view/LifecycleView.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.util.Log
6 | import android.view.View
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleEventObserver
9 | import androidx.lifecycle.LifecycleOwner
10 |
11 | /**
12 | * see https://xuyisheng.top/lifecycle/
13 | */
14 | class LifecycleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, lifecycleOwner: LifecycleOwner)
15 | : View(context, attrs, defStyleAttr), LifecycleEventObserver {
16 |
17 | init {
18 | Log.i("wutao--> ", "init: ")
19 | lifecycleOwner.lifecycle.addObserver(this)
20 | }
21 |
22 | fun release() {
23 | Log.i("wutao--> ", "release")
24 | }
25 |
26 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
27 | when (event) {
28 | Lifecycle.Event.ON_DESTROY -> {
29 | release()
30 | source.lifecycle.removeObserver(this)
31 | }
32 | Lifecycle
33 | .Event.ON_RESUME -> {
34 | Log.i("wutao--> ", "ON_RESUME: ")
35 | }
36 | }
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_contained_btn_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_contained_btn_stroke.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_contained_btn_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_dashed_btn_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_outlined_btn_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_outlined_btn_stroke.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_outlined_btn_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_text_btn_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_md_text_btn_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button_dash_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_live_data_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
21 |
22 |
27 |
28 |
34 |
35 |
40 |
41 |
42 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F16222
4 | #00000000
5 |
6 |
7 | #000000
8 | #33000000
9 |
10 |
11 | #FFFFFF
12 | #E0E0E0
13 | #EDEDED
14 |
15 |
16 | #F2D7D4
17 | #E5B0AA
18 | #BF382A
19 | #610F07
20 | #380600
21 |
22 |
23 | #FCDDCF
24 | #E5AD93
25 | #F16222
26 | #612307
27 | #381200
28 |
29 |
30 | #FDECD0
31 | #FAD7A0
32 | #F39C12
33 | #613E07
34 | #382300
35 |
36 |
37 | #FCF6CF
38 | #FAEEA0
39 | #F2D411
40 | #615507
41 | #383100
42 |
43 |
44 | #CCE8D4
45 | #99D1AA
46 | #0DBF42
47 | #006E21
48 | #003811
49 |
50 |
51 | #CCEFF0
52 | #99DFE1
53 | #00AFB5
54 | #00696D
55 | #003638
56 |
57 |
58 | #CFDFFC
59 | #A0BFFA
60 | #0D4ABF
61 | #00266E
62 | #001438
63 |
64 |
65 | #EDCFFC
66 | #DCA0FA
67 | #840DBF
68 | #49006E
69 | #250038
70 |
71 |
72 | #EDEFF2
73 | #DCDEE0
74 | #C4C8CC
75 | #A2A5A8
76 | #5E6770
77 | #435160
78 | #2B343D
79 | #1D2329
80 | #171C21
81 | #707070
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 11sp
4 | 12sp
5 | 14sp
6 | 16sp
7 | 18sp
8 | 20sp
9 | 24sp
10 | 28sp
11 | 40sp
12 |
13 | 0dp
14 | 1dp
15 | 2dp
16 | 3dp
17 | 4dp
18 | 6dp
19 | 8dp
20 | 10dp
21 | 12dp
22 | 14dp
23 | 15dp
24 | 16dp
25 | 18dp
26 | 20dp
27 | 24dp
28 | 30dp
29 | 32dp
30 | 36dp
31 | 40dp
32 | 48dp
33 | 50dp
34 | 54dp
35 | 56dp
36 | 60dp
37 | 64dp
38 | 72dp
39 | 76dp
40 | 80dp
41 | 100dp
42 | 110dp
43 | 118dp
44 | 120dp
45 | 130dp
46 | 136dp
47 | 150dp
48 | 160dp
49 | 180dp
50 | 198dp
51 | 200dp
52 | 240dp
53 | 265dp
54 | 280dp
55 | 400dp
56 | 300dp
57 | 340dp
58 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidCommonCode
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
33 |
34 |
52 |
53 |
70 |
71 |
83 |
84 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ldlywt/commoncode/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.commoncode
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath "com.android.tools.build:gradle:7.0.2"
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | task clean(type: Delete) {
17 | delete rootProject.buildDir
18 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Oct 28 14:19:26 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/ktx/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ktx/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'com.ldlywt.ktx'
8 | compileSdk 33
9 |
10 | defaultConfig {
11 | minSdk 26
12 | targetSdk 33
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | kotlinOptions {
29 | jvmTarget = '1.8'
30 | }
31 | }
32 |
33 | dependencies {
34 |
35 | implementation 'androidx.core:core-ktx:1.10.1'
36 | implementation 'androidx.appcompat:appcompat:1.6.1'
37 | implementation 'com.google.android.material:material:1.9.0'
38 | testImplementation 'junit:junit:4.13.2'
39 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
41 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
42 | }
--------------------------------------------------------------------------------
/ktx/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ldlywt/AndroidCommonCode/dc49f984a15c312ce6f30f672c6810f41c47a7cd/ktx/consumer-rules.pro
--------------------------------------------------------------------------------
/ktx/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
--------------------------------------------------------------------------------
/ktx/src/androidTest/java/com/ldlywt/ktx/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ldlywt.ktx.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/ktx/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/ActivityKtx.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Bundle
6 |
7 | inline fun Activity.startActivity(bundle: Bundle? = null) {
8 | val intent = Intent(this, T::class.java)
9 | if (bundle != null) {
10 | intent.putExtras(bundle)
11 | }
12 | startActivity(intent)
13 | }
14 |
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/Cursor.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.base.ktx
2 |
3 | import android.annotation.SuppressLint
4 | import android.database.Cursor
5 | import androidx.core.database.getIntOrNull
6 | import androidx.core.database.getLongOrNull
7 |
8 | @SuppressLint("Range")
9 | fun Cursor.getStringValue(key: String): String = getString(getColumnIndex(key)) ?: ""
10 |
11 | @SuppressLint("Range")
12 | fun Cursor.getStringValueOrNull(key: String): String? = getString(getColumnIndex(key))
13 |
14 | @SuppressLint("Range")
15 | fun Cursor.getIntValue(key: String): Int = getIntOrNull(getColumnIndex(key)) ?: 0
16 |
17 | fun Cursor.getIntValueOrNull(key: String): Int? = getIntOrNull(getColumnIndex(key))
18 |
19 | @SuppressLint("Range")
20 | fun Cursor.getLongValue(key: String): Long = getLongOrNull(getColumnIndex(key)) ?: 0L
21 |
22 | fun Cursor.getLongValueOrNull(key: String): Long? = getLongOrNull(getColumnIndex(key))
23 |
24 | //fun Cursor.getTimeValue(key: String): Instant = Instant.fromEpochMilliseconds(getLongValueOrNull(key) ?: 0L)
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/EditText.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 |
4 | import android.widget.EditText
5 |
6 | val EditText.value
7 | get() = text?.toString() ?: ""
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/File.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.base.ktx
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import java.io.BufferedInputStream
6 | import java.io.BufferedOutputStream
7 | import java.io.File
8 | import java.io.FileOutputStream
9 | import java.io.IOException
10 |
11 | fun File.getDirectChildrenCount(countHiddenItems: Boolean): Int {
12 | return listFiles()?.filter {
13 | if (countHiddenItems) {
14 | true
15 | } else {
16 | !it.name.startsWith('.')
17 | }
18 | }?.size ?: 0
19 | }
20 |
21 | fun File.newName(): String {
22 | var index = 1
23 | var candidate: String
24 | val split = nameWithoutExtension.split(' ').toMutableList()
25 | val last = split.last()
26 | if ("""^\(\d+\)$""".toRegex().matches(last)) {
27 | split.removeLast()
28 | }
29 | val name = split.joinToString(" ")
30 | while (true) {
31 | candidate = if (extension.isEmpty()) "$name ($index)" else "$name ($index).$extension"
32 | if (!File("$parent/$candidate").exists()) {
33 | return candidate
34 | }
35 | index++
36 | }
37 | }
38 |
39 | fun File.newPath(): String {
40 | return "$parent/" + newName()
41 | }
42 |
43 | fun copyFile(context: Context, pathFrom: Uri, pathTo: String) {
44 | context.contentResolver.openInputStream(pathFrom).use { input ->
45 | var bis: BufferedInputStream? = null
46 | var bos: BufferedOutputStream? = null
47 |
48 | try {
49 | bis = BufferedInputStream(input)
50 | bos = BufferedOutputStream(FileOutputStream(pathTo, false))
51 | val buf = ByteArray(1024)
52 | bis.read(buf)
53 | do {
54 | bos.write(buf)
55 | } while (bis.read(buf) != -1)
56 | } catch (e: IOException) {
57 | e.printStackTrace()
58 | } finally {
59 | try {
60 | bis?.close()
61 | bos?.close()
62 | } catch (e: IOException) {
63 | e.printStackTrace()
64 | }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/FlowKtx.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import androidx.lifecycle.Lifecycle
4 | import androidx.lifecycle.LifecycleOwner
5 | import androidx.lifecycle.lifecycleScope
6 | import androidx.lifecycle.repeatOnLifecycle
7 | import kotlinx.coroutines.CoroutineScope
8 | import kotlinx.coroutines.flow.Flow
9 | import kotlinx.coroutines.launch
10 |
11 |
12 | inline fun Flow.launchAndCollectIn(
13 | owner: LifecycleOwner,
14 | minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
15 | crossinline action: suspend CoroutineScope.(T) -> Unit
16 | ) = owner.lifecycleScope.launch {
17 | owner.repeatOnLifecycle(minActiveState) {
18 | collect {
19 | action(it)
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/Network.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import android.net.NetworkCapabilities
6 | import androidx.fragment.app.Fragment
7 |
8 | fun Context.isNetworkAvailable(): Boolean {
9 | val manager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
10 | val capabilities = manager.getNetworkCapabilities(manager.activeNetwork)
11 | return if (capabilities != null) {
12 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
13 | || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
14 | || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
15 | } else false
16 | }
17 |
18 | fun Fragment.isNetworkAvailable() = requireContext().isNetworkAvailable()
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/StringKtx.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.content.Context
4 | import androidx.annotation.StringRes
5 |
6 |
7 | fun Context.stringOf(@StringRes id: Int, vararg formatArgs: Any): String = getString(id, *formatArgs)
8 |
9 | fun Context.stringOf(@StringRes id: Int): String = getString(id)
10 |
11 | fun Context.getString(@StringRes id: Int, vararg formatArgs: Any?): String {
12 | return applicationContext.getString(id, *formatArgs)
13 | }
14 |
15 | inline fun String.getEmptyOrDefault(default: () -> T): T {
16 | return if (isNullOrEmpty() || this == "null") {
17 | default()
18 | } else {
19 | this as T
20 | }
21 | }
22 |
23 | package com.ldlywt.base.ktx
24 |
25 | import android.provider.MediaStore
26 | import com.ldlywt.base.global.Constants
27 | import java.text.Normalizer
28 | import java.util.Locale
29 | import kotlin.math.ceil
30 | fun String.getFilenameWithoutExtension() = substringBeforeLast(".")
31 |
32 | fun String.getFilenameExtension() = substring(lastIndexOf(".") + 1)
33 |
34 | // remove diacritics, for example č -> c
35 | fun String.normalizeString(): String = Normalizer.normalize(this, Normalizer.Form.NFD).replace("\\p{InCombiningDiacriticalMarks}+".toRegex(), "")
36 |
37 | operator fun String.times(x: Int): String {
38 | val stringBuilder = StringBuilder()
39 | for (i in 1..x) {
40 | stringBuilder.append(this)
41 | }
42 | return stringBuilder.toString()
43 | }
44 |
45 | fun String.isTextFile(): Boolean {
46 | val mime = getMimeType()
47 | if (mime.startsWith("text/") || mime.startsWith("application/")) {
48 | return true
49 | }
50 |
51 | return false
52 | }
53 |
54 | fun String.isPdfFile() = getFilenameExtension().equals("pdf", true)
55 | fun String.isZipFile() = getFilenameExtension().equals("zip", true)
56 | fun String.isVideoFast() = Constants.VIDEO_EXTENSIONS.any { endsWith(it, true) }
57 | fun String.isPartialSupportVideo() = Constants.PARTIAL_SUPPORT_VIDEO_EXTENSIONS.any { endsWith(it, true) }
58 | fun String.isImageFast() = Constants.PHOTO_EXTENSIONS.any { endsWith(it, true) }
59 | fun String.isAudioFast() = Constants.AUDIO_EXTENSIONS.any { endsWith(it, true) }
60 | fun String.isRawFast() = Constants.RAW_EXTENSIONS.any { endsWith(it, true) }
61 |
62 | fun String.isImageSlow() = isImageFast() || getMimeType().startsWith("image") || startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
63 | fun String.isVideoSlow() = isVideoFast() || getMimeType().startsWith("video") || startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString())
64 | fun String.isAudioSlow() = isAudioFast() || getMimeType().startsWith("audio") || startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())
65 |
66 | fun String.canModifyEXIF() = Constants.SUPPORTING_EXIF_EXTENSIONS.any { endsWith(it, true) }
67 |
68 | private val typesMap = HashMap().apply {
69 | put("323", "text/h323")
70 | put("3g2", "video/3gpp2")
71 | put("3gp", "video/3gpp")
72 | put("3gp2", "video/3gpp2")
73 | put("3gpp", "video/3gpp")
74 | put("7z", "application/x-7z-compressed")
75 | put("aa", "audio/audible")
76 | put("aac", "audio/aac")
77 | put("aaf", "application/octet-stream")
78 | put("aax", "audio/vnd.audible.aax")
79 | put("ac3", "audio/ac3")
80 | put("aca", "application/octet-stream")
81 | put("accda", "application/msaccess.addin")
82 | put("accdb", "application/msaccess")
83 | put("accdc", "application/msaccess.cab")
84 | put("accde", "application/msaccess")
85 | put("accdr", "application/msaccess.runtime")
86 | put("accdt", "application/msaccess")
87 | put("accdw", "application/msaccess.webapplication")
88 | put("accft", "application/msaccess.ftemplate")
89 | put("acx", "application/internet-property-stream")
90 | put("addin", "text/xml")
91 | put("ade", "application/msaccess")
92 | put("adobebridge", "application/x-bridge-url")
93 | put("adp", "application/msaccess")
94 | put("adt", "audio/vnd.dlna.adts")
95 | put("adts", "audio/aac")
96 | put("afm", "application/octet-stream")
97 | put("ai", "application/postscript")
98 | put("aif", "audio/aiff")
99 | put("aifc", "audio/aiff")
100 | put("aiff", "audio/aiff")
101 | put("air", "application/vnd.adobe.air-application-installer-package+zip")
102 | put("amc", "application/mpeg")
103 | put("anx", "application/annodex")
104 | put("apk", "application/vnd.android.package-archive")
105 | put("application", "application/x-ms-application")
106 | put("art", "image/x-jg")
107 | put("asa", "application/xml")
108 | put("asax", "application/xml")
109 | put("ascx", "application/xml")
110 | put("asd", "application/octet-stream")
111 | put("asf", "video/x-ms-asf")
112 | put("ashx", "application/xml")
113 | put("asi", "application/octet-stream")
114 | put("asm", "text/plain")
115 | put("asmx", "application/xml")
116 | put("aspx", "application/xml")
117 | put("asr", "video/x-ms-asf")
118 | put("asx", "video/x-ms-asf")
119 | put("atom", "application/atom+xml")
120 | put("au", "audio/basic")
121 | put("avi", "video/x-msvideo")
122 | put("axa", "audio/annodex")
123 | put("axs", "application/olescript")
124 | put("axv", "video/annodex")
125 | put("bas", "text/plain")
126 | put("bcpio", "application/x-bcpio")
127 | put("bin", "application/octet-stream")
128 | put("bmp", "image/bmp")
129 | put("c", "text/plain")
130 | put("cab", "application/octet-stream")
131 | put("caf", "audio/x-caf")
132 | put("calx", "application/vnd.ms-office.calx")
133 | put("cat", "application/vnd.ms-pki.seccat")
134 | put("cc", "text/plain")
135 | put("cd", "text/plain")
136 | put("cdda", "audio/aiff")
137 | put("cdf", "application/x-cdf")
138 | put("cer", "application/x-x509-ca-cert")
139 | put("cfg", "text/plain")
140 | put("chm", "application/octet-stream")
141 | put("class", "application/x-java-applet")
142 | put("clp", "application/x-msclip")
143 | put("cmd", "text/plain")
144 | put("cmx", "image/x-cmx")
145 | put("cnf", "text/plain")
146 | put("cod", "image/cis-cod")
147 | put("config", "application/xml")
148 | put("conf", "text/plain")
149 | put("contact", "text/x-ms-contact")
150 | put("coverage", "application/xml")
151 | put("cpio", "application/x-cpio")
152 | put("cpp", "text/plain")
153 | put("crd", "application/x-mscardfile")
154 | put("crl", "application/pkix-crl")
155 | put("crt", "application/x-x509-ca-cert")
156 | put("cs", "text/plain")
157 | put("csdproj", "text/plain")
158 | put("csh", "application/x-csh")
159 | put("csproj", "text/plain")
160 | put("css", "text/css")
161 | put("csv", "text/csv")
162 | put("cur", "application/octet-stream")
163 | put("cxx", "text/plain")
164 | put("dat", "application/octet-stream")
165 | put("datasource", "application/xml")
166 | put("dbproj", "text/plain")
167 | put("dcr", "application/x-director")
168 | put("def", "text/plain")
169 | put("deploy", "application/octet-stream")
170 | put("der", "application/x-x509-ca-cert")
171 | put("dgml", "application/xml")
172 | put("dib", "image/bmp")
173 | put("dif", "video/x-dv")
174 | put("dir", "application/x-director")
175 | put("disco", "text/xml")
176 | put("divx", "video/divx")
177 | put("dll", "application/x-msdownload")
178 | put("dll.config", "text/xml")
179 | put("dlm", "text/dlm")
180 | put("dng", "image/x-adobe-dng")
181 | put("doc", "application/msword")
182 | put("docm", "application/vnd.ms-word.document.macroEnabled.12")
183 | put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
184 | put("dot", "application/msword")
185 | put("dotm", "application/vnd.ms-word.template.macroEnabled.12")
186 | put("dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template")
187 | put("dsp", "application/octet-stream")
188 | put("dsw", "text/plain")
189 | put("dtd", "text/xml")
190 | put("dtsconfig", "text/xml")
191 | put("dv", "video/x-dv")
192 | put("dvi", "application/x-dvi")
193 | put("dwf", "drawing/x-dwf")
194 | put("dwp", "application/octet-stream")
195 | put("dxr", "application/x-director")
196 | put("eml", "message/rfc822")
197 | put("emz", "application/octet-stream")
198 | put("eot", "application/vnd.ms-fontobject")
199 | put("eps", "application/postscript")
200 | put("etl", "application/etl")
201 | put("etx", "text/x-setext")
202 | put("evy", "application/envoy")
203 | put("exe", "application/octet-stream")
204 | put("exe.config", "text/xml")
205 | put("fdf", "application/vnd.fdf")
206 | put("fif", "application/fractals")
207 | put("filters", "application/xml")
208 | put("fla", "application/octet-stream")
209 | put("flac", "audio/flac")
210 | put("flr", "x-world/x-vrml")
211 | put("flv", "video/x-flv")
212 | put("fsscript", "application/fsharp-script")
213 | put("fsx", "application/fsharp-script")
214 | put("generictest", "application/xml")
215 | put("gif", "image/gif")
216 | put("group", "text/x-ms-group")
217 | put("gsm", "audio/x-gsm")
218 | put("gtar", "application/x-gtar")
219 | put("gz", "application/x-gzip")
220 | put("h", "text/plain")
221 | put("hdf", "application/x-hdf")
222 | put("hdml", "text/x-hdml")
223 | put("hhc", "application/x-oleobject")
224 | put("hhk", "application/octet-stream")
225 | put("hhp", "application/octet-stream")
226 | put("hlp", "application/winhlp")
227 | put("hpp", "text/plain")
228 | put("hqx", "application/mac-binhex40")
229 | put("hta", "application/hta")
230 | put("htc", "text/x-component")
231 | put("htm", "text/html")
232 | put("html", "text/html")
233 | put("htt", "text/webviewhtml")
234 | put("hxa", "application/xml")
235 | put("hxc", "application/xml")
236 | put("hxd", "application/octet-stream")
237 | put("hxe", "application/xml")
238 | put("hxf", "application/xml")
239 | put("hxh", "application/octet-stream")
240 | put("hxi", "application/octet-stream")
241 | put("hxk", "application/xml")
242 | put("hxq", "application/octet-stream")
243 | put("hxr", "application/octet-stream")
244 | put("hxs", "application/octet-stream")
245 | put("hxt", "text/html")
246 | put("hxv", "application/xml")
247 | put("hxw", "application/octet-stream")
248 | put("hxx", "text/plain")
249 | put("i", "text/plain")
250 | put("ico", "image/x-icon")
251 | put("ics", "text/calendar")
252 | put("idl", "text/plain")
253 | put("ief", "image/ief")
254 | put("iii", "application/x-iphone")
255 | put("inc", "text/plain")
256 | put("inf", "application/octet-stream")
257 | put("ini", "text/plain")
258 | put("inl", "text/plain")
259 | put("ins", "application/x-internet-signup")
260 | put("ipa", "application/x-itunes-ipa")
261 | put("ipg", "application/x-itunes-ipg")
262 | put("ipproj", "text/plain")
263 | put("ipsw", "application/x-itunes-ipsw")
264 | put("iqy", "text/x-ms-iqy")
265 | put("isp", "application/x-internet-signup")
266 | put("ite", "application/x-itunes-ite")
267 | put("itlp", "application/x-itunes-itlp")
268 | put("itms", "application/x-itunes-itms")
269 | put("itpc", "application/x-itunes-itpc")
270 | put("ivf", "video/x-ivf")
271 | put("jar", "application/java-archive")
272 | put("java", "application/octet-stream")
273 | put("jck", "application/liquidmotion")
274 | put("jcz", "application/liquidmotion")
275 | put("jfif", "image/pjpeg")
276 | put("jnlp", "application/x-java-jnlp-file")
277 | put("jpb", "application/octet-stream")
278 | put("jpe", "image/jpeg")
279 | put("jpeg", "image/jpeg")
280 | put("jpg", "image/jpeg")
281 | put("js", "application/javascript")
282 | put("json", "application/json")
283 | put("jsx", "text/jscript")
284 | put("jsxbin", "text/plain")
285 | put("latex", "application/x-latex")
286 | put("library-ms", "application/windows-library+xml")
287 | put("lit", "application/x-ms-reader")
288 | put("loadtest", "application/xml")
289 | put("log", "text/plain")
290 | put("lpk", "application/octet-stream")
291 | put("lsf", "video/x-la-asf")
292 | put("lst", "text/plain")
293 | put("lsx", "video/x-la-asf")
294 | put("lzh", "application/octet-stream")
295 | put("m13", "application/x-msmediaview")
296 | put("m14", "application/x-msmediaview")
297 | put("m1v", "video/mpeg")
298 | put("m2t", "video/vnd.dlna.mpeg-tts")
299 | put("m2ts", "video/vnd.dlna.mpeg-tts")
300 | put("m2v", "video/mpeg")
301 | put("m3u", "audio/x-mpegurl")
302 | put("m3u8", "audio/x-mpegurl")
303 | put("m4a", "audio/m4a")
304 | put("m4b", "audio/m4b")
305 | put("m4p", "audio/m4p")
306 | put("m4r", "audio/x-m4r")
307 | put("m4v", "video/x-m4v")
308 | put("mac", "image/x-macpaint")
309 | put("mak", "text/plain")
310 | put("man", "application/x-troff-man")
311 | put("manifest", "application/x-ms-manifest")
312 | put("map", "text/plain")
313 | put("master", "application/xml")
314 | put("md", "text/plain")
315 | put("mda", "application/msaccess")
316 | put("mdb", "application/x-msaccess")
317 | put("mde", "application/msaccess")
318 | put("mdp", "application/octet-stream")
319 | put("me", "application/x-troff-me")
320 | put("mfp", "application/x-shockwave-flash")
321 | put("mht", "message/rfc822")
322 | put("mhtml", "message/rfc822")
323 | put("mid", "audio/mid")
324 | put("midi", "audio/mid")
325 | put("mix", "application/octet-stream")
326 | put("mk", "text/plain")
327 | put("mkv", "video/x-matroska")
328 | put("mmf", "application/x-smaf")
329 | put("mno", "text/xml")
330 | put("mny", "application/x-msmoney")
331 | put("mod", "video/mpeg")
332 | put("mov", "video/quicktime")
333 | put("movie", "video/x-sgi-movie")
334 | put("mp2", "video/mpeg")
335 | put("mp2v", "video/mpeg")
336 | put("mp3", "audio/mpeg")
337 | put("mp4", "video/mp4")
338 | put("mp4v", "video/mp4")
339 | put("mpa", "video/mpeg")
340 | put("mpe", "video/mpeg")
341 | put("mpeg", "video/mpeg")
342 | put("mpf", "application/vnd.ms-mediapackage")
343 | put("mpg", "video/mpeg")
344 | put("mpp", "application/vnd.ms-project")
345 | put("mpv2", "video/mpeg")
346 | put("mqv", "video/quicktime")
347 | put("ms", "application/x-troff-ms")
348 | put("msi", "application/octet-stream")
349 | put("mso", "application/octet-stream")
350 | put("mts", "video/vnd.dlna.mpeg-tts")
351 | put("mtx", "application/xml")
352 | put("mvb", "application/x-msmediaview")
353 | put("mvc", "application/x-miva-compiled")
354 | put("mxp", "application/x-mmxp")
355 | put("nc", "application/x-netcdf")
356 | put("nsc", "video/x-ms-asf")
357 | put("nws", "message/rfc822")
358 | put("ocx", "application/octet-stream")
359 | put("oda", "application/oda")
360 | put("odb", "application/vnd.oasis.opendocument.database")
361 | put("odc", "application/vnd.oasis.opendocument.chart")
362 | put("odf", "application/vnd.oasis.opendocument.formula")
363 | put("odg", "application/vnd.oasis.opendocument.graphics")
364 | put("odh", "text/plain")
365 | put("odi", "application/vnd.oasis.opendocument.image")
366 | put("odl", "text/plain")
367 | put("odm", "application/vnd.oasis.opendocument.text-master")
368 | put("odp", "application/vnd.oasis.opendocument.presentation")
369 | put("ods", "application/vnd.oasis.opendocument.spreadsheet")
370 | put("odt", "application/vnd.oasis.opendocument.text")
371 | put("oga", "audio/ogg")
372 | put("ogg", "audio/ogg")
373 | put("ogv", "video/ogg")
374 | put("ogx", "application/ogg")
375 | put("one", "application/onenote")
376 | put("onea", "application/onenote")
377 | put("onepkg", "application/onenote")
378 | put("onetmp", "application/onenote")
379 | put("onetoc", "application/onenote")
380 | put("onetoc2", "application/onenote")
381 | put("opml", "text/xml")
382 | put("opus", "audio/ogg")
383 | put("orderedtest", "application/xml")
384 | put("osdx", "application/opensearchdescription+xml")
385 | put("otf", "application/font-sfnt")
386 | put("otg", "application/vnd.oasis.opendocument.graphics-template")
387 | put("oth", "application/vnd.oasis.opendocument.text-web")
388 | put("otp", "application/vnd.oasis.opendocument.presentation-template")
389 | put("ots", "application/vnd.oasis.opendocument.spreadsheet-template")
390 | put("ott", "application/vnd.oasis.opendocument.text-template")
391 | put("oxt", "application/vnd.openofficeorg.extension")
392 | put("ovpn", "text/plain")
393 | put("p10", "application/pkcs10")
394 | put("p12", "application/x-pkcs12")
395 | put("p7b", "application/x-pkcs7-certificates")
396 | put("p7c", "application/pkcs7-mime")
397 | put("p7m", "application/pkcs7-mime")
398 | put("p7r", "application/x-pkcs7-certreqresp")
399 | put("p7s", "application/pkcs7-signature")
400 | put("pbm", "image/x-portable-bitmap")
401 | put("pcast", "application/x-podcast")
402 | put("pct", "image/pict")
403 | put("pcx", "application/octet-stream")
404 | put("pcz", "application/octet-stream")
405 | put("pdf", "application/pdf")
406 | put("pfb", "application/octet-stream")
407 | put("pfm", "application/octet-stream")
408 | put("pfx", "application/x-pkcs12")
409 | put("pgm", "image/x-portable-graymap")
410 | put("php", "text/plain")
411 | put("pic", "image/pict")
412 | put("pict", "image/pict")
413 | put("pkgdef", "text/plain")
414 | put("pkgundef", "text/plain")
415 | put("pko", "application/vnd.ms-pki.pko")
416 | put("pls", "audio/scpls")
417 | put("pma", "application/x-perfmon")
418 | put("pmc", "application/x-perfmon")
419 | put("pml", "application/x-perfmon")
420 | put("pmr", "application/x-perfmon")
421 | put("pmw", "application/x-perfmon")
422 | put("png", "image/png")
423 | put("pnm", "image/x-portable-anymap")
424 | put("pnt", "image/x-macpaint")
425 | put("pntg", "image/x-macpaint")
426 | put("pnz", "image/png")
427 | put("pot", "application/vnd.ms-powerpoint")
428 | put("potm", "application/vnd.ms-powerpoint.template.macroEnabled.12")
429 | put("potx", "application/vnd.openxmlformats-officedocument.presentationml.template")
430 | put("ppa", "application/vnd.ms-powerpoint")
431 | put("ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12")
432 | put("ppm", "image/x-portable-pixmap")
433 | put("pps", "application/vnd.ms-powerpoint")
434 | put("ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12")
435 | put("ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow")
436 | put("ppt", "application/vnd.ms-powerpoint")
437 | put("pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12")
438 | put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation")
439 | put("prf", "application/pics-rules")
440 | put("prm", "application/octet-stream")
441 | put("prx", "application/octet-stream")
442 | put("ps", "application/postscript")
443 | put("psc1", "application/PowerShell")
444 | put("psd", "application/octet-stream")
445 | put("psess", "application/xml")
446 | put("psm", "application/octet-stream")
447 | put("psp", "application/octet-stream")
448 | put("pub", "application/x-mspublisher")
449 | put("pwz", "application/vnd.ms-powerpoint")
450 | put("py", "text/plain")
451 | put("qht", "text/x-html-insertion")
452 | put("qhtm", "text/x-html-insertion")
453 | put("qt", "video/quicktime")
454 | put("qti", "image/x-quicktime")
455 | put("qtif", "image/x-quicktime")
456 | put("qtl", "application/x-quicktimeplayer")
457 | put("qxd", "application/octet-stream")
458 | put("ra", "audio/x-pn-realaudio")
459 | put("ram", "audio/x-pn-realaudio")
460 | put("rar", "application/x-rar-compressed")
461 | put("ras", "image/x-cmu-raster")
462 | put("rat", "application/rat-file")
463 | put("rb", "text/plain")
464 | put("rc", "text/plain")
465 | put("rc2", "text/plain")
466 | put("rct", "text/plain")
467 | put("rdlc", "application/xml")
468 | put("reg", "text/plain")
469 | put("resx", "application/xml")
470 | put("rf", "image/vnd.rn-realflash")
471 | put("rgb", "image/x-rgb")
472 | put("rgs", "text/plain")
473 | put("rm", "application/vnd.rn-realmedia")
474 | put("rmi", "audio/mid")
475 | put("rmp", "application/vnd.rn-rn_music_package")
476 | put("roff", "application/x-troff")
477 | put("rpm", "audio/x-pn-realaudio-plugin")
478 | put("rqy", "text/x-ms-rqy")
479 | put("rtf", "application/rtf")
480 | put("rtx", "text/richtext")
481 | put("ruleset", "application/xml")
482 | put("s", "text/plain")
483 | put("safariextz", "application/x-safari-safariextz")
484 | put("scd", "application/x-msschedule")
485 | put("scr", "text/plain")
486 | put("sct", "text/scriptlet")
487 | put("sd2", "audio/x-sd2")
488 | put("sdp", "application/sdp")
489 | put("sea", "application/octet-stream")
490 | put("searchConnector-ms", "application/windows-search-connector+xml")
491 | put("setpay", "application/set-payment-initiation")
492 | put("setreg", "application/set-registration-initiation")
493 | put("settings", "application/xml")
494 | put("sgimb", "application/x-sgimb")
495 | put("sgml", "text/sgml")
496 | put("sh", "application/x-sh")
497 | put("shar", "application/x-shar")
498 | put("shtml", "text/html")
499 | put("sit", "application/x-stuffit")
500 | put("sitemap", "application/xml")
501 | put("skin", "application/xml")
502 | put("sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12")
503 | put("sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide")
504 | put("slk", "application/vnd.ms-excel")
505 | put("sln", "text/plain")
506 | put("slupkg-ms", "application/x-ms-license")
507 | put("smd", "audio/x-smd")
508 | put("smi", "application/octet-stream")
509 | put("smx", "audio/x-smd")
510 | put("smz", "audio/x-smd")
511 | put("snd", "audio/basic")
512 | put("snippet", "application/xml")
513 | put("snp", "application/octet-stream")
514 | put("sol", "text/plain")
515 | put("sor", "text/plain")
516 | put("spc", "application/x-pkcs7-certificates")
517 | put("spl", "application/futuresplash")
518 | put("spx", "audio/ogg")
519 | put("src", "application/x-wais-source")
520 | put("srf", "text/plain")
521 | put("ssisdeploymentmanifest", "text/xml")
522 | put("ssm", "application/streamingmedia")
523 | put("sst", "application/vnd.ms-pki.certstore")
524 | put("stl", "application/vnd.ms-pki.stl")
525 | put("sv4cpio", "application/x-sv4cpio")
526 | put("sv4crc", "application/x-sv4crc")
527 | put("svc", "application/xml")
528 | put("svg", "image/svg+xml")
529 | put("swf", "application/x-shockwave-flash")
530 | put("t", "application/x-troff")
531 | put("tar", "application/x-tar")
532 | put("tcl", "application/x-tcl")
533 | put("testrunconfig", "application/xml")
534 | put("testsettings", "application/xml")
535 | put("tex", "application/x-tex")
536 | put("texi", "application/x-texinfo")
537 | put("texinfo", "application/x-texinfo")
538 | put("tgz", "application/x-compressed")
539 | put("thmx", "application/vnd.ms-officetheme")
540 | put("thn", "application/octet-stream")
541 | put("tif", "image/tiff")
542 | put("tiff", "image/tiff")
543 | put("tlh", "text/plain")
544 | put("tli", "text/plain")
545 | put("toc", "application/octet-stream")
546 | put("tr", "application/x-troff")
547 | put("trm", "application/x-msterminal")
548 | put("trx", "application/xml")
549 | put("ts", "video/vnd.dlna.mpeg-tts")
550 | put("tsv", "text/tab-separated-values")
551 | put("ttf", "application/font-sfnt")
552 | put("tts", "video/vnd.dlna.mpeg-tts")
553 | put("txt", "text/plain")
554 | put("u32", "application/octet-stream")
555 | put("uls", "text/iuls")
556 | put("user", "text/plain")
557 | put("ustar", "application/x-ustar")
558 | put("vb", "text/plain")
559 | put("vbdproj", "text/plain")
560 | put("vbk", "video/mpeg")
561 | put("vbproj", "text/plain")
562 | put("vbs", "text/vbscript")
563 | put("vcf", "text/x-vcard")
564 | put("vcproj", "application/xml")
565 | put("vcs", "text/calendar")
566 | put("vcxproj", "application/xml")
567 | put("vddproj", "text/plain")
568 | put("vdp", "text/plain")
569 | put("vdproj", "text/plain")
570 | put("vdx", "application/vnd.ms-visio.viewer")
571 | put("vml", "text/xml")
572 | put("vscontent", "application/xml")
573 | put("vsct", "text/xml")
574 | put("vsd", "application/vnd.visio")
575 | put("vsi", "application/ms-vsi")
576 | put("vsix", "application/vsix")
577 | put("vsixlangpack", "text/xml")
578 | put("vsixmanifest", "text/xml")
579 | put("vsmdi", "application/xml")
580 | put("vspscc", "text/plain")
581 | put("vss", "application/vnd.visio")
582 | put("vsscc", "text/plain")
583 | put("vssettings", "text/xml")
584 | put("vssscc", "text/plain")
585 | put("vst", "application/vnd.visio")
586 | put("vstemplate", "text/xml")
587 | put("vsto", "application/x-ms-vsto")
588 | put("vsw", "application/vnd.visio")
589 | put("vsx", "application/vnd.visio")
590 | put("vtx", "application/vnd.visio")
591 | put("wav", "audio/wav")
592 | put("wave", "audio/wav")
593 | put("wax", "audio/x-ms-wax")
594 | put("wbk", "application/msword")
595 | put("wbmp", "image/vnd.wap.wbmp")
596 | put("wcm", "application/vnd.ms-works")
597 | put("wdb", "application/vnd.ms-works")
598 | put("wdp", "image/vnd.ms-photo")
599 | put("webarchive", "application/x-safari-webarchive")
600 | put("webm", "video/webm")
601 | put("webp", "image/webp")
602 | put("webtest", "application/xml")
603 | put("wiq", "application/xml")
604 | put("wiz", "application/msword")
605 | put("wks", "application/vnd.ms-works")
606 | put("wlmp", "application/wlmoviemaker")
607 | put("wlpginstall", "application/x-wlpg-detect")
608 | put("wlpginstall3", "application/x-wlpg3-detect")
609 | put("wm", "video/x-ms-wm")
610 | put("wma", "audio/x-ms-wma")
611 | put("wmd", "application/x-ms-wmd")
612 | put("wmf", "application/x-msmetafile")
613 | put("wml", "text/vnd.wap.wml")
614 | put("wmlc", "application/vnd.wap.wmlc")
615 | put("wmls", "text/vnd.wap.wmlscript")
616 | put("wmlsc", "application/vnd.wap.wmlscriptc")
617 | put("wmp", "video/x-ms-wmp")
618 | put("wmv", "video/x-ms-wmv")
619 | put("wmx", "video/x-ms-wmx")
620 | put("wmz", "application/x-ms-wmz")
621 | put("woff", "application/font-woff")
622 | put("wpl", "application/vnd.ms-wpl")
623 | put("wps", "application/vnd.ms-works")
624 | put("wri", "application/x-mswrite")
625 | put("wrl", "x-world/x-vrml")
626 | put("wrz", "x-world/x-vrml")
627 | put("wsc", "text/scriptlet")
628 | put("wsdl", "text/xml")
629 | put("wvx", "video/x-ms-wvx")
630 | put("x", "application/directx")
631 | put("xaf", "x-world/x-vrml")
632 | put("xaml", "application/xaml+xml")
633 | put("xap", "application/x-silverlight-app")
634 | put("xbap", "application/x-ms-xbap")
635 | put("xbm", "image/x-xbitmap")
636 | put("xdr", "text/plain")
637 | put("xht", "application/xhtml+xml")
638 | put("xhtml", "application/xhtml+xml")
639 | put("xla", "application/vnd.ms-excel")
640 | put("xlam", "application/vnd.ms-excel.addin.macroEnabled.12")
641 | put("xlc", "application/vnd.ms-excel")
642 | put("xld", "application/vnd.ms-excel")
643 | put("xlk", "application/vnd.ms-excel")
644 | put("xll", "application/vnd.ms-excel")
645 | put("xlm", "application/vnd.ms-excel")
646 | put("xls", "application/vnd.ms-excel")
647 | put("xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12")
648 | put("xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12")
649 | put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
650 | put("xlt", "application/vnd.ms-excel")
651 | put("xltm", "application/vnd.ms-excel.template.macroEnabled.12")
652 | put("xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template")
653 | put("xlw", "application/vnd.ms-excel")
654 | put("xml", "text/xml")
655 | put("xmta", "application/xml")
656 | put("xof", "x-world/x-vrml")
657 | put("xoml", "text/plain")
658 | put("xpm", "image/x-xpixmap")
659 | put("xps", "application/vnd.ms-xpsdocument")
660 | put("xrm-ms", "text/xml")
661 | put("xsc", "application/xml")
662 | put("xsd", "text/xml")
663 | put("xsf", "text/xml")
664 | put("xsl", "text/xml")
665 | put("xslt", "text/xml")
666 | put("xsn", "application/octet-stream")
667 | put("xss", "application/xml")
668 | put("xspf", "application/xspf+xml")
669 | put("xtp", "application/octet-stream")
670 | put("xwd", "image/x-xwindowdump")
671 | put("yaml", "text/plain")
672 | put("yml", "text/plain")
673 | put("z", "application/x-compress")
674 | put("zip", "application/zip")
675 | }
676 |
677 | fun String.getMimeType(): String {
678 | return typesMap[getFilenameExtension().lowercase()] ?: ""
679 | }
680 |
681 | fun String.indexOf(anyOfTheseChars: Array): Int {
682 | for (element in anyOfTheseChars) {
683 | for (j in 0 until this.length) {
684 | if (this[j] == element) {
685 | return j
686 | }
687 | }
688 | }
689 | return 0
690 | }
691 |
692 | fun String.splitInParts(len: Int, splitChars: Array): Array {
693 | // Length of search for dividable characters
694 | val subdivisionSize = 50
695 | val result = arrayOfNulls(ceil(this.length.toDouble() / len.toDouble()).toInt())
696 | var cutStart = 0
697 | for (i in result.indices) {
698 | var cutEnd = this.length.coerceAtMost((i + 1) * len)
699 | if (cutEnd != this.length && (cutEnd + subdivisionSize) < this.length) {
700 | val section = this.substring(cutEnd - subdivisionSize, cutEnd + subdivisionSize)
701 | val index = section.indexOf(splitChars)
702 | cutEnd = cutEnd - subdivisionSize + index + 1
703 | }
704 | result[i] = this.substring(cutStart, cutEnd)
705 | cutStart = cutEnd
706 | }
707 | return result.requireNoNulls()
708 | }
709 |
710 | fun String.capitalize(): String {
711 | return this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
712 | }
713 |
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/Version.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.os.Build
4 | import androidx.annotation.ChecksSdkIntAtLeast
5 |
6 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
7 | fun isQPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
8 |
9 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
10 | fun isRPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
11 |
12 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
13 | fun isSPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
14 |
15 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S_V2)
16 | fun isSV2Plus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2
17 |
18 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
19 | fun isTIRAMISUPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/ViewKtx.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.view.View
4 |
5 | fun View.clickWithLimit(intervalMill: Int = 500, block: ((v: View?) -> Unit)) {
6 | setOnClickListener(object : View.OnClickListener {
7 | var last = 0L
8 | override fun onClick(v: View?) {
9 | if (System.currentTimeMillis() - last > intervalMill) {
10 | block(v)
11 | last = System.currentTimeMillis()
12 | }
13 | }
14 | })
15 | }
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/clipboard.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.content.ClipData
4 | import android.content.ClipboardManager
5 | import android.content.Context
6 | import androidx.core.content.ContextCompat
7 |
8 | fun String.copyToClipboard(context: Context) {
9 | val clipboardManager = ContextCompat.getSystemService(context, ClipboardManager::class.java)
10 | val clip = ClipData.newPlainText("clipboard", this)
11 | clipboardManager?.setPrimaryClip(clip)
12 | }
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/dp.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.content.res.Resources
4 |
5 | // Convert px to dp
6 | val Int.dp: Int
7 | get() = (this / Resources.getSystem().displayMetrics.density).toInt()
8 |
9 | //Convert dp to px
10 | val Int.px: Int
11 | get() = (this * Resources.getSystem().displayMetrics.density).toInt()
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/hidekeyboard.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.app.Activity
4 | import android.view.View
5 | import android.view.inputmethod.InputMethodManager
6 | import androidx.fragment.app.Fragment
7 |
8 | fun Activity.hideKeyboard() {
9 | val imm: InputMethodManager =
10 | getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
11 | val view = currentFocus ?: View(this)
12 | imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
13 | }
14 |
15 | fun Fragment.hideKeyboard() {
16 | activity?.apply {
17 | val imm: InputMethodManager =
18 | getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
19 | val view = currentFocus ?: View(this)
20 | imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
21 | }
22 | }
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/snackbar.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.view.View
4 | import androidx.annotation.StringRes
5 | import com.google.android.material.snackbar.Snackbar
6 |
7 | fun View.snackbar(message: String, duration: Int = Snackbar.LENGTH_LONG) {
8 | Snackbar.make(this, message, duration).show()
9 | }
10 |
11 | fun View.snackbar(@StringRes message: Int, duration: Int = Snackbar.LENGTH_LONG) {
12 | Snackbar.make(this, message, duration).show()
13 | }
--------------------------------------------------------------------------------
/ktx/src/main/java/com/ldlywt/ktx/toast.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import android.app.Activity
4 | import android.widget.Toast
5 | import androidx.annotation.StringRes
6 | import androidx.fragment.app.Fragment
7 |
8 | fun Fragment.toast(message: String) {
9 | Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
10 | }
11 |
12 | fun Fragment.toast(@StringRes message: Int) {
13 | Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
14 | }
15 |
16 | fun Activity.toast(message: String) {
17 | Toast.makeText(this, message, Toast.LENGTH_LONG).show()
18 | }
19 |
20 | fun Activity.toast(@StringRes message: Int) {
21 | Toast.makeText(this, message, Toast.LENGTH_LONG).show()
22 | }
--------------------------------------------------------------------------------
/ktx/src/test/java/com/ldlywt/ktx/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ldlywt.ktx
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | }
8 | }
9 | rootProject.name = "AndroidCommonCode"
10 | include ':app'
11 | include ':ktx'
12 |
--------------------------------------------------------------------------------