├── extension
├── consumer-rules.pro
├── .gitignore
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── mobven
│ │ │ │ └── extension
│ │ │ │ ├── Int.kt
│ │ │ │ ├── Long.kt
│ │ │ │ ├── Boolean.kt
│ │ │ │ ├── Constants.kt
│ │ │ │ ├── Date.kt
│ │ │ │ ├── Lifecycle.kt
│ │ │ │ ├── Collection.kt
│ │ │ │ ├── Double.kt
│ │ │ │ ├── definitions
│ │ │ │ └── CreditCardType.kt
│ │ │ │ ├── dialogs
│ │ │ │ ├── FullScreenDialogFragment.kt
│ │ │ │ ├── MaterialAlertDialog.kt
│ │ │ │ ├── CustomAlertDialog.kt
│ │ │ │ └── BottomSheetExposer.kt
│ │ │ │ ├── Event.kt
│ │ │ │ ├── String.kt
│ │ │ │ ├── View.kt
│ │ │ │ └── Context.kt
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ │ └── values
│ │ │ └── styles.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── mobven
│ │ │ └── extension
│ │ │ ├── ExampleUnitTest.kt
│ │ │ └── StringUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mobven
│ │ └── extension
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── google_maps_api.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── themes.xml
│ │ │ │ └── strings.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── drawable
│ │ │ │ ├── bg_bottom.xml
│ │ │ │ ├── ic_baseline_check.xml
│ │ │ │ ├── icons_32_ic_32_salary_account_white.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── fragment_flow.xml
│ │ │ │ ├── fragment_text.xml
│ │ │ │ ├── activity_geofence.xml
│ │ │ │ ├── activity_maps.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── fragment_menu.xml
│ │ │ │ ├── item_horizontal_feed.xml
│ │ │ │ ├── activity_concat_example.xml
│ │ │ │ ├── dialog_full_screen.xml
│ │ │ │ ├── item_menu.xml
│ │ │ │ ├── custom_dialog.xml
│ │ │ │ ├── fragment_single_selectable_rv.xml
│ │ │ │ ├── activity_custom_shadow.xml
│ │ │ │ ├── activity_choose_from_gallery.xml
│ │ │ │ ├── dialog_bottom_sheet.xml
│ │ │ │ ├── item_horizontal_sub_rv.xml
│ │ │ │ ├── item_selectable.xml
│ │ │ │ └── activity_view_ext_demo.xml
│ │ │ ├── values-v23
│ │ │ │ └── themes.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── navigation
│ │ │ │ └── nav_main.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── mobven
│ │ │ │ └── extensions
│ │ │ │ ├── recyclerview
│ │ │ │ ├── singleselectable
│ │ │ │ │ ├── SelectableModel.kt
│ │ │ │ │ ├── SingleSelectableRecyclerViewFragment.kt
│ │ │ │ │ └── SingleSelectableAdapter.kt
│ │ │ │ ├── diffutiladapter
│ │ │ │ │ ├── MyDiffCallback.kt
│ │ │ │ │ ├── MyDiffUtilAdapter.kt
│ │ │ │ │ └── DiffUtilRecyclerViewFragment.kt
│ │ │ │ └── concatadapter
│ │ │ │ │ ├── HorizontalItemAdapter.kt
│ │ │ │ │ ├── HorizontalFeedAdapter.kt
│ │ │ │ │ └── ConcatExampleActivity.kt
│ │ │ │ ├── compose
│ │ │ │ ├── layout
│ │ │ │ │ ├── ui
│ │ │ │ │ │ └── theme
│ │ │ │ │ │ │ ├── Color.kt
│ │ │ │ │ │ │ ├── Shape.kt
│ │ │ │ │ │ │ ├── Type.kt
│ │ │ │ │ │ │ └── Theme.kt
│ │ │ │ │ └── LayoutComposeActivity.kt
│ │ │ │ ├── SampleData.kt
│ │ │ │ └── ComposePlaygroundActivity.kt
│ │ │ │ ├── flow
│ │ │ │ ├── FlowFragment.kt
│ │ │ │ └── FlowViewModel.kt
│ │ │ │ ├── customshadow
│ │ │ │ └── CustomShadowActivity.kt
│ │ │ │ ├── test
│ │ │ │ └── ExtensionTestFragment.kt
│ │ │ │ ├── Menu.kt
│ │ │ │ ├── MenuAdapter.kt
│ │ │ │ ├── onactivityresultapi
│ │ │ │ └── ChooseFromGalleryActivity.kt
│ │ │ │ ├── PowerManager.java
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── InputManager.java
│ │ │ │ ├── remindmethere
│ │ │ │ ├── GeofenceHelper.kt
│ │ │ │ ├── GeofenceBroadcastReceiver.kt
│ │ │ │ ├── NotificationHelper.kt
│ │ │ │ ├── GeofenceForegroundService.kt
│ │ │ │ └── MapsActivity.kt
│ │ │ │ ├── MenuFragment.kt
│ │ │ │ └── ViewExtDemoActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── mobven
│ │ │ └── extensions
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mobven
│ │ └── extensions
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── CODEOWNERS
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── sonarlint
│ └── issuestore
│ │ └── index.pb
├── .gitignore
└── codeStyles
│ └── codeStyleConfig.xml
├── LICENSE
├── gradle.properties
├── gradlew.bat
├── README.md
├── .gitignore
└── gradlew
/extension/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @abalta-mobven
2 |
--------------------------------------------------------------------------------
/extension/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':extension'
2 | include ':app'
3 | rootProject.name = "MobvenExtensions"
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 8dp
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/index.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/.idea/sonarlint/issuestore/index.pb
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Int.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | fun Int?.orZero(): Int = this ?: 0
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | /jarRepositories.xml
5 | /kotlinScripting.xml
6 |
7 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Long.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | fun Long?.orZero(): Long = this ?: 0L
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Boolean.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | fun Boolean?.orFalse(): Boolean = this ?: false
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobven/Extensify/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/extension/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | object Constants {
4 | const val MIME_TYPE_IMAGE = "image/*"
5 | const val MIME_TYPE_VIDEO = "video/*"
6 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/google_maps_api.xml:
--------------------------------------------------------------------------------
1 |
2 | AIzaSyB_RBtfO3jF9N6tfRLlULEat4soyLEKF94
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/singleselectable/SelectableModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.singleselectable
2 |
3 | data class SelectableModel(
4 | var title: String,
5 | var isSelected: Boolean = false
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 21 12:49:56 EET 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/compose/layout/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.compose.layout.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_flow.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_geofence.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/extension/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/compose/layout/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.compose.layout.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/app/src/main/res/values-v23/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Date.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import java.text.SimpleDateFormat
4 | import java.util.*
5 |
6 | /**
7 | * Extension method that converts date for given format
8 | */
9 | fun Date.formatToViewTime(customFormat: String = "dd MMMM yyyy"): String {
10 | val sdf = SimpleDateFormat(customFormat, Locale.getDefault())
11 | Calendar.getInstance().time
12 | return sdf.format(this)
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_check.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/flow/FlowFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.flow
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import androidx.fragment.app.viewModels
6 |
7 | class FlowFragment: Fragment() {
8 | private val viewModel by viewModels()
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 | viewModel
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/mobven/extensions/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions
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 | }
--------------------------------------------------------------------------------
/extension/src/test/java/com/mobven/extension/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
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 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/customshadow/CustomShadowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.customshadow
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 | import com.mobven.extensions.R
6 |
7 | class CustomShadowActivity : AppCompatActivity() {
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | setContentView(R.layout.activity_custom_shadow)
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_maps.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extension/src/test/java/com/mobven/extension/StringUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import org.junit.Assert
4 | import org.junit.Test
5 |
6 | class StringUnitTest {
7 |
8 | @Test
9 | fun tcknTests() {
10 | Assert.assertFalse(null.isValidTCKN())
11 | Assert.assertFalse("".isValidTCKN())
12 | Assert.assertFalse("234".isValidTCKN())
13 | Assert.assertFalse("00719505186".isValidTCKN())
14 | Assert.assertTrue("20519505186".isValidTCKN())
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #D81B60
11 | #9600574b
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Lifecycle.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import androidx.lifecycle.LifecycleOwner
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.Observer
6 |
7 | /**
8 | * Extension method for observing livedata
9 | */
10 | fun LifecycleOwner.observe(liveData: LiveData?, observer: (T) -> Unit) {
11 | liveData?.observe(this, Observer(observer))
12 | }
13 |
14 | /**
15 | * Extension method for observing events
16 | */
17 | fun LifecycleOwner.eventObserve(liveData: LiveData>?, observer: (T) -> Unit) {
18 | liveData?.observe(this, EventObserver(observer))
19 | }
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Collection.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | /**
4 | * Moves the given item at the `oldIndex` to the `newIndex`
5 | */
6 | fun MutableList.moveAt(oldIndex: Int, newIndex: Int) {
7 | val item = this[oldIndex]
8 | removeAt(oldIndex)
9 | if (oldIndex > newIndex)
10 | add(newIndex, item)
11 | else
12 | add(newIndex - 1, item)
13 | }
14 |
15 | /**
16 | * Moves the given **T** item to the specified index
17 | */
18 | fun MutableList.move(item: T, newIndex: Int) {
19 | val currentIndex = indexOf(item)
20 | if (currentIndex < 0) return
21 | removeAt(currentIndex)
22 | add(newIndex, item)
23 | }
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Double.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import java.lang.IllegalArgumentException
4 | import java.text.NumberFormat
5 | import java.util.*
6 |
7 | /**
8 | * Return double value or zero if it is null
9 | */
10 | fun Double?.orZero(): Double = this ?: 0.0
11 |
12 | /**
13 | * Convert double to number with given locale currency
14 | */
15 | fun Double?.localizedNumberFormat(loc: Locale = Locale.getDefault()): String {
16 | try {
17 | val nf = NumberFormat.getCurrencyInstance(loc)
18 | if (this == 0.0) {
19 | return "0"
20 | }
21 | return nf.format(this)
22 | } catch (ex: IllegalArgumentException) {
23 | return "0"
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_horizontal_feed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/test/ExtensionTestFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.test
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import com.mobven.extension.makeCall
9 | import com.mobven.extensions.R
10 |
11 | class ExtensionTestFragment: Fragment(R.layout.fragment_text) {
12 |
13 | override fun onCreateView(
14 | inflater: LayoutInflater,
15 | container: ViewGroup?,
16 | savedInstanceState: Bundle?
17 | ): View? {
18 | requireContext().makeCall("5542029938")
19 | return super.onCreateView(inflater, container, savedInstanceState)
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_concat_example.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/mobven/extensions/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions
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.mobven.extensions", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/extension/src/androidTest/java/com/mobven/extension/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
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.mobven.extension.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/extension/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/main/res/layout/dialog_full_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/Menu.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions;
2 |
3 | import androidx.annotation.StringDef
4 | import com.mobven.extensions.Menu.Companion.SINGLE_SELECT_LIST
5 | import com.mobven.extensions.Menu.Companion.VIEW_EXT
6 |
7 | @StringDef(SINGLE_SELECT_LIST, VIEW_EXT)
8 | annotation class Menu {
9 | companion object {
10 | const val SINGLE_SELECT_LIST = "Single Selectable RecyclerView"
11 | const val VIEW_EXT = "View Ext."
12 | const val REQUEST_PERMISSIONS = "Request Permissions"
13 | const val COMPOSE_PLAYGROUND = "Compose Playground"
14 | const val LAYOUT_COMPOSE = "Compose Layout"
15 | const val CONCAT_ADAPTER = "Concat Adapter"
16 | const val DIFF_UTIL_LIST = "Diff Util RecyclerView"
17 | const val GEOFENCE = "Geofence"
18 | const val CUSTOM_SHADOW = "Custom Shadow"
19 | const val FLOW_EXAMPLE = "Flow Examples"
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/compose/layout/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.compose.layout.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/app/src/main/res/layout/custom_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_single_selectable_rv.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_custom_shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icons_32_ic_32_salary_account_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
19 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Mobven
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/definitions/CreditCardType.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension.definitions
2 |
3 | import androidx.annotation.StringDef
4 | import com.mobven.extension.definitions.CreditCardType.Companion.AMEX
5 | import com.mobven.extension.definitions.CreditCardType.Companion.DEFAULT
6 | import com.mobven.extension.definitions.CreditCardType.Companion.MAESTRO
7 | import com.mobven.extension.definitions.CreditCardType.Companion.MASTERCARD
8 | import com.mobven.extension.definitions.CreditCardType.Companion.TROY
9 | import com.mobven.extension.definitions.CreditCardType.Companion.VISA
10 |
11 | @StringDef(VISA, MASTERCARD, MAESTRO, TROY, AMEX, DEFAULT)
12 | annotation class CreditCardType {
13 | companion object {
14 | const val VISA = "^4[0-9]{6,}\$"
15 | const val MASTERCARD = "^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}\$"
16 | const val MAESTRO = "^(?:5[0678]\\\\d\\\\d|6304|6390|67\\\\d\\\\d)\\\\d{8,15}\$"
17 | const val TROY = "^(?:9792|65\\d{2}|36|2205)\\d{12}\$"
18 | const val AMEX = "^3[47][0-9]{5,}\$"
19 | const val DEFAULT = "default"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/MenuAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.mobven.extension.click
7 | import com.mobven.extensions.databinding.ItemMenuBinding
8 |
9 | class MenuAdapter(private val menuList: List) :
10 | RecyclerView.Adapter() {
11 |
12 | var menuClick: (String) -> Unit = {}
13 |
14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
15 | val binding = ItemMenuBinding.inflate(LayoutInflater.from(parent.context), parent, false)
16 | return ViewHolder(binding)
17 | }
18 |
19 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
20 | holder.bind(menuList[position])
21 | }
22 |
23 | override fun getItemCount(): Int = menuList.size
24 |
25 | inner class ViewHolder(private val binding: ItemMenuBinding) :
26 | RecyclerView.ViewHolder(binding.root) {
27 | fun bind(item: String) {
28 | binding.apply {
29 | tvMenu.text = item
30 | root.click {
31 | menuClick.invoke(item)
32 | }
33 | }
34 | }
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/extension/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 33
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 33
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_17
25 | targetCompatibility JavaVersion.VERSION_17
26 | }
27 | kotlinOptions {
28 | jvmTarget = '17'
29 | }
30 | namespace 'com.mobven.extension'
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 | implementation 'androidx.browser:browser:1.5.0'
39 | testImplementation 'junit:junit:4.13.2'
40 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
42 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_choose_from_gallery.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
23 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/diffutiladapter/MyDiffCallback.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.diffutiladapter
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 |
5 | //DiffUtil is a utility class that can calculate the difference between two lists and
6 | // output a list of update operations that converts the first list into the second one.
7 |
8 | //It can be used to calculate updates for a RecyclerView Adapter.
9 |
10 | open class MyDiffCallback(
11 | private val oldGalaxies: List,
12 | private val newGalaxies: List
13 | ): DiffUtil.Callback() {
14 |
15 | override fun getOldListSize(): Int {
16 | return oldGalaxies.size
17 | }
18 |
19 | override fun getNewListSize(): Int {
20 | return newGalaxies.size
21 | }
22 |
23 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
24 | // In the real world you need to compare something unique like id
25 | return oldGalaxies[oldItemPosition] == newGalaxies[newItemPosition]
26 | }
27 |
28 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
29 | // This is called if areItemsTheSame() == true;
30 | return oldGalaxies[oldItemPosition] == newGalaxies[newItemPosition]
31 | }
32 | }
33 | //end
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/onactivityresultapi/ChooseFromGalleryActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.onactivityresultapi
2 |
3 | import android.net.Uri
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.bumptech.glide.Glide
7 | import com.mobven.extension.Constants
8 | import com.mobven.extension.chooseFromGallery
9 | import com.mobven.extension.click
10 | import com.mobven.extensions.databinding.ActivityChooseFromGalleryBinding
11 |
12 | class ChooseFromGalleryActivity : AppCompatActivity() {
13 | private val binding by lazy {
14 | ActivityChooseFromGalleryBinding.inflate(layoutInflater)
15 | }
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | setContentView(binding.root)
19 |
20 | binding.apply {
21 | val chooser = this@ChooseFromGalleryActivity.chooseFromGallery { uriList: List ->
22 | Glide.with(this@ChooseFromGalleryActivity).load(uriList.last()).into(ivPreview)
23 | }
24 | btnChoosePhotoFromGallery.click {
25 | chooser.launch(Constants.MIME_TYPE_IMAGE)
26 | }
27 | btnChooseVideoFromGallery.click {
28 | chooser.launch(Constants.MIME_TYPE_VIDEO)
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_bottom_sheet.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
20 |
21 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/dialogs/FullScreenDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension.dialogs
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.DialogFragment
9 | import androidx.fragment.app.FragmentManager
10 |
11 | class FullScreenDialogFragment(private val dialogTheme: Int, private val viewHolderCreator: (inflater: LayoutInflater, dialog: FullScreenDialogFragment) -> View?) :
12 | DialogFragment() {
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | setStyle(STYLE_NORMAL, dialogTheme)
17 | }
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater,
21 | container: ViewGroup?,
22 | savedInstanceState: Bundle?
23 | ): View? {
24 | return viewHolderCreator(inflater, this)
25 | }
26 | }
27 |
28 | fun fullScreenDialogOf(
29 | fragmentManager: FragmentManager,
30 | dialogTheme: Int = android.R.style.Theme_Material_NoActionBar_TranslucentDecor,
31 | viewHolderCreator: (inflater: LayoutInflater, dialog: FullScreenDialogFragment) -> View?
32 | ) {
33 | FullScreenDialogFragment(dialogTheme, viewHolderCreator).show(fragmentManager, "FULL_SCREEN_DIALOG")
34 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_horizontal_sub_rv.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/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
22 | android.defaults.buildfeatures.buildconfig=true
23 | android.nonTransitiveRClass=false
24 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/compose/layout/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.compose.layout.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = Purple200,
11 | primaryVariant = Purple700,
12 | secondary = Teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = Purple500,
17 | primaryVariant = Purple700,
18 | secondary = Teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun MobvenExtensionsTheme(
32 | darkTheme: Boolean = isSystemInDarkTheme(),
33 | content: @Composable() () -> Unit
34 | ) {
35 | val colors = if (darkTheme) {
36 | DarkColorPalette
37 | } else {
38 | LightColorPalette
39 | }
40 |
41 | MaterialTheme(
42 | colors = colors,
43 | typography = Typography,
44 | shapes = Shapes,
45 | content = content
46 | )
47 | }
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Event.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import androidx.lifecycle.Observer
4 |
5 | /**
6 | * Used as a wrapper for data that is exposed via a LiveData that represents an event.
7 | */
8 | open class Event(private val content: T) {
9 |
10 | @Suppress("MemberVisibilityCanBePrivate")
11 | var hasBeenHandled = false
12 | private set // Allow external read but not write
13 |
14 | /**
15 | * Returns the content and prevents its use again.
16 | */
17 | fun getContentIfNotHandled(): T? {
18 | return if (hasBeenHandled) {
19 | null
20 | } else {
21 | hasBeenHandled = true
22 | content
23 | }
24 | }
25 |
26 | /**
27 | * Returns the content, even if it's already been handled.
28 | */
29 | fun peekContent(): T = content
30 | }
31 |
32 | /**
33 | * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
34 | * already been handled.
35 | *
36 | * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
37 | */
38 | class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> {
39 | override fun onChanged(event: Event?) {
40 | event?.getContentIfNotHandled()?.let {
41 | onEventUnhandledContent(it)
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/PowerManager.java:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Build;
5 | import android.os.IInterface;
6 | import android.util.Log;
7 |
8 | import java.lang.reflect.InvocationTargetException;
9 | import java.lang.reflect.Method;
10 |
11 | public final class PowerManager {
12 | private final IInterface manager;
13 | private Method isScreenOnMethod;
14 |
15 | public PowerManager(IInterface manager) {
16 | this.manager = manager;
17 | }
18 |
19 | private Method getIsScreenOnMethod() throws NoSuchMethodException {
20 | if (isScreenOnMethod == null) {
21 | @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
22 | String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
23 | isScreenOnMethod = manager.getClass().getMethod(methodName);
24 | }
25 | return isScreenOnMethod;
26 | }
27 |
28 | public boolean isScreenOn() {
29 | try {
30 | Method method = getIsScreenOnMethod();
31 | return (boolean) method.invoke(manager);
32 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
33 | Log.e("SCRCPY", "Could not invoke method", e);
34 | return false;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/dialogs/MaterialAlertDialog.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension.dialogs
2 |
3 | import android.content.Context
4 | import android.content.DialogInterface
5 | import androidx.annotation.StyleRes
6 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
7 |
8 | fun Context.alert(
9 | @StyleRes style: Int = 0,
10 | dialogBuilder: MaterialAlertDialogBuilder.() -> Unit
11 | ) {
12 | MaterialAlertDialogBuilder(this, style)
13 | .apply {
14 | setCancelable(false)
15 | dialogBuilder()
16 | create()
17 | show()
18 | }
19 | }
20 |
21 | fun MaterialAlertDialogBuilder.negativeButton(
22 | text: String = "No",
23 | handleClick: (dialogInterface: DialogInterface) -> Unit = { it.dismiss() }
24 | ) {
25 | this.setNegativeButton(text) { dialogInterface, _ -> handleClick(dialogInterface) }
26 | }
27 |
28 | fun MaterialAlertDialogBuilder.positiveButton(
29 | text: String = "Yes",
30 | handleClick: (dialogInterface: DialogInterface) -> Unit = { it.dismiss() }
31 | ) {
32 | this.setPositiveButton(text) { dialogInterface, _ -> handleClick(dialogInterface) }
33 | }
34 |
35 | fun MaterialAlertDialogBuilder.neutralButton(
36 | text: String = "OK",
37 | handleClick: (dialogInterface: DialogInterface) -> Unit = { it.dismiss() }
38 | ) {
39 | this.setNeutralButton(text) { dialogInterface, _ -> handleClick(dialogInterface) }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
7 | import com.mobven.extensions.databinding.ActivityMainBinding
8 |
9 | class MainActivity : AppCompatActivity() {
10 |
11 | private lateinit var binding: ActivityMainBinding
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | Log.i("Lifecycle", "onCreate")
16 | installSplashScreen()
17 | binding = ActivityMainBinding.inflate(layoutInflater)
18 | setContentView(binding.root)
19 | }
20 |
21 | override fun onStart() {
22 | super.onStart()
23 | Log.i("Lifecycle", "onStart")
24 | }
25 |
26 | override fun onResume() {
27 | super.onResume()
28 | Log.i("Lifecycle", "onResume")
29 | }
30 |
31 | override fun onRestart() {
32 | super.onRestart()
33 | Log.i("Lifecycle", "onRestart")
34 | }
35 |
36 | override fun onPause() {
37 | super.onPause()
38 | Log.i("Lifecycle", "onPause")
39 | }
40 |
41 | override fun onStop() {
42 | super.onStop()
43 | Log.i("Lifecycle", "onStop")
44 | }
45 |
46 | override fun onDestroy() {
47 | super.onDestroy()
48 | Log.i("Lifecycle", "onDestroy")
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_selectable.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
21 |
22 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/concatadapter/HorizontalItemAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.concatadapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.mobven.extensions.databinding.ItemHorizontalSubRvBinding
7 |
8 | class HorizontalItemAdapter :
9 | RecyclerView.Adapter() {
10 |
11 | private val dataList = mutableListOf()
12 |
13 | var itemClick: (String) -> Unit = {}
14 |
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
16 | val layoutInflater = LayoutInflater.from(parent.context)
17 | val item = ItemHorizontalSubRvBinding.inflate(layoutInflater, parent, false)
18 | return ViewHolder(item)
19 | }
20 |
21 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
22 | holder.bindData(dataList[position])
23 | }
24 |
25 | override fun getItemCount(): Int = dataList.size
26 |
27 | fun setData(list: MutableList) {
28 | dataList.clear()
29 | dataList.addAll(list)
30 | notifyDataSetChanged()
31 | }
32 |
33 | inner class ViewHolder(private val binding: ItemHorizontalSubRvBinding) :
34 | RecyclerView.ViewHolder(binding.root) {
35 | fun bindData(title: String) {
36 | binding.tvTitle.text = title
37 | binding.tvTitle.setOnClickListener {
38 | itemClick.invoke(title)
39 | }
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/concatadapter/HorizontalFeedAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.concatadapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.mobven.extensions.databinding.ItemHorizontalFeedBinding
7 |
8 | class HorizontalFeedAdapter:
9 | RecyclerView.Adapter() {
10 |
11 | private val dataList = mutableListOf()
12 | private val subAdapter = HorizontalItemAdapter()
13 |
14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
15 | val layoutInflater = LayoutInflater.from(parent.context)
16 | val item = ItemHorizontalFeedBinding.inflate(layoutInflater, parent, false)
17 | return ViewHolder(item)
18 | }
19 |
20 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
21 | holder.bindData(dataList)
22 | }
23 |
24 | override fun getItemCount(): Int = 1
25 |
26 | fun setData(list: MutableList) {
27 | dataList.clear()
28 | dataList.addAll(list)
29 | notifyDataSetChanged()
30 | }
31 |
32 | inner class ViewHolder(private val binding: ItemHorizontalFeedBinding) :
33 | RecyclerView.ViewHolder(binding.root) {
34 | fun bindData(list: MutableList) {
35 | binding.apply {
36 | rvHorizontalList.adapter = subAdapter.apply {
37 | setData(list)
38 | }
39 | }
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/singleselectable/SingleSelectableRecyclerViewFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.singleselectable
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import androidx.recyclerview.widget.DividerItemDecoration
9 | import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
10 | import com.mobven.extensions.databinding.FragmentSingleSelectableRvBinding
11 |
12 | class SingleSelectableRecyclerViewFragment : Fragment() {
13 |
14 | private var _binding: FragmentSingleSelectableRvBinding? = null
15 | private val binding get() = _binding!!
16 |
17 | override fun onCreateView(
18 | inflater: LayoutInflater,
19 | container: ViewGroup?,
20 | savedInstanceState: Bundle?
21 | ): View {
22 | _binding = FragmentSingleSelectableRvBinding.inflate(inflater, container, false)
23 | return binding.root
24 | }
25 |
26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27 | super.onViewCreated(view, savedInstanceState)
28 | initData()
29 | }
30 |
31 |
32 | override fun onDestroyView() {
33 | super.onDestroyView()
34 | _binding = null
35 | }
36 |
37 | private fun initData() {
38 | val data = mutableListOf()
39 | repeat((0..10).count()) {
40 | data.add(SelectableModel("Selectable Model $it"))
41 | }
42 | binding.rvSingleSelectable.apply {
43 | adapter = SingleSelectableAdapter(data)
44 | addItemDecoration(DividerItemDecoration(requireContext(), VERTICAL))
45 | }
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/concatadapter/ConcatExampleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.concatadapter
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.recyclerview.widget.ConcatAdapter
6 | import com.mobven.extensions.MenuAdapter
7 | import com.mobven.extensions.databinding.ActivityConcatExampleBinding
8 |
9 | class ConcatExampleActivity: AppCompatActivity() {
10 |
11 | private lateinit var binding: ActivityConcatExampleBinding
12 |
13 | private val horizontalFeedAdapter = HorizontalFeedAdapter()
14 | private val horizontalFeedAdapter2 = HorizontalFeedAdapter()
15 | private val menuAdapter = MenuAdapter(listOf("dssad","dsasda","dssad","dsasda","dssad","dsasda","dssad","dsasda"))
16 |
17 | private val concatAdapter = ConcatAdapter(
18 | horizontalFeedAdapter,
19 | menuAdapter,
20 | )
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | binding = ActivityConcatExampleBinding.inflate(layoutInflater)
25 | setContentView(binding.root)
26 | horizontalFeedAdapter2.setData(getDummyData())
27 | horizontalFeedAdapter.setData(getDummyData2())
28 | concatAdapter.addAdapter(0,horizontalFeedAdapter2)
29 | binding.rvActivity.adapter = concatAdapter
30 | }
31 |
32 | private fun getDummyData() = mutableListOf(
33 | "sil",
34 | "DATA2",
35 | "DATA23",
36 | "DATA34",
37 | "DATA35",
38 | "DATA31",
39 | "DATA10",
40 | "DSADSA",
41 | "WWW",
42 | "dsadsFFadsa",
43 | "UUUU",
44 | "TTTT",
45 | "TTRTRRTRTRTTR"
46 | )
47 |
48 | private fun getDummyData2() = mutableListOf(
49 | "sil",
50 | "dsa",
51 | "asd",
52 | "dsa",
53 | "DATA35",
54 | )
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/diffutiladapter/MyDiffUtilAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.diffutiladapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.DiffUtil
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.mobven.extensions.databinding.ItemMenuBinding
8 |
9 | /**
10 | * This is the same RecyclerView but using DiffUtil instead of
11 | * notifyDataSetChanged() to animate changes
12 | */
13 | class MyDiffUtilAdapter(var galaxies: List) :
14 | RecyclerView.Adapter() {
15 |
16 | var itemClickListener: ((position: Int, name: String) -> Unit)? = null
17 |
18 | override fun onCreateViewHolder(parent: ViewGroup, viewHolderType: Int): ViewHolder {
19 | val binding =
20 | ItemMenuBinding.inflate(LayoutInflater.from(parent.context), parent, false)
21 | return ViewHolder(binding)
22 | }
23 |
24 | override fun getItemCount(): Int = galaxies.size
25 |
26 | override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
27 | viewHolder.bindView(galaxies[position], position)
28 | }
29 |
30 | inner class ViewHolder(private val binding: ItemMenuBinding) :
31 | RecyclerView.ViewHolder(binding.root) {
32 | fun bindView(galaxy: String, position: Int) {
33 | binding.apply {
34 | tvMenu.text = galaxy
35 | itemView.setOnClickListener { itemClickListener?.invoke(position, galaxy) }
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * THIS IS THE ONLY DIFFERENCE BETWEEN the regular MyNotifyDataSetAdapter
42 | */
43 | fun updateList(newGalaxies: List) {
44 | val diffCallback = MyDiffCallback(galaxies, newGalaxies)
45 | val diffResult = DiffUtil.calculateDiff(diffCallback)
46 | diffResult.dispatchUpdatesTo(this)
47 |
48 | galaxies = newGalaxies
49 | }
50 | }
51 | //end
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_view_ext_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
20 |
21 |
26 |
27 |
32 |
33 |
38 |
39 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/InputManager.java:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions;
2 |
3 | import android.util.Log;
4 | import android.view.InputEvent;
5 |
6 | import java.lang.reflect.InvocationTargetException;
7 | import java.lang.reflect.Method;
8 |
9 | public final class InputManager {
10 |
11 | public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0;
12 | public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
13 | public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
14 |
15 | private final android.hardware.input.InputManager manager;
16 | private Method injectInputEventMethod;
17 |
18 | private static Method setDisplayIdMethod;
19 |
20 | public InputManager(android.hardware.input.InputManager manager) {
21 | this.manager = manager;
22 | }
23 |
24 | private Method getInjectInputEventMethod() throws NoSuchMethodException {
25 | if (injectInputEventMethod == null) {
26 | injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
27 | }
28 | return injectInputEventMethod;
29 | }
30 |
31 | public boolean injectInputEvent(InputEvent inputEvent, int mode) {
32 | try {
33 | Method method = getInjectInputEventMethod();
34 | return (boolean) method.invoke(manager, inputEvent, mode);
35 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
36 | Log.e("SCRCPY", "Could not invoke method", e);
37 | return false;
38 | }
39 | }
40 |
41 | private static Method getSetDisplayIdMethod() throws NoSuchMethodException {
42 | if (setDisplayIdMethod == null) {
43 | setDisplayIdMethod = InputEvent.class.getMethod("setDisplayId", int.class);
44 | }
45 | return setDisplayIdMethod;
46 | }
47 |
48 | public static boolean setDisplayId(InputEvent inputEvent, int displayId) {
49 | try {
50 | Method method = getSetDisplayIdMethod();
51 | method.invoke(inputEvent, displayId);
52 | return true;
53 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
54 | Log.e("SCRCPY", "Cannot associate a display id to the input event", e);
55 | return false;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/diffutiladapter/DiffUtilRecyclerViewFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.diffutiladapter
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import com.mobven.extension.click
9 | import com.mobven.extension.toast
10 | import com.mobven.extensions.databinding.FragmentSingleSelectableRvBinding
11 |
12 | class DiffUtilRecyclerViewFragment : Fragment() {
13 |
14 | private var _binding: FragmentSingleSelectableRvBinding? = null
15 | private val binding get() = _binding!!
16 | private lateinit var adapter: MyDiffUtilAdapter
17 |
18 | private val stars = listOf(
19 | "Aldebaran",
20 | "Rigel",
21 | "Canopus",
22 | "Bellatrix",
23 | "Polaris",
24 | "Regulus",
25 | "VY Canis Majoris",
26 | "UY Scuti",
27 | "Deneb",
28 | "Wezen",
29 | "Arcturus"
30 | )
31 | private val galaxies = listOf(
32 | "Circunus",
33 | "Milky Way",
34 | "Andromeda",
35 | "StarBust",
36 | "Sombrero",
37 | "Pinwheel",
38 | "Cartwheel",
39 | "Large Magellonic Cloud"
40 | )
41 |
42 | override fun onCreateView(
43 | inflater: LayoutInflater,
44 | container: ViewGroup?,
45 | savedInstanceState: Bundle?
46 | ): View {
47 | _binding = FragmentSingleSelectableRvBinding.inflate(inflater, container, false)
48 | return binding.root
49 | }
50 |
51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
52 | super.onViewCreated(view, savedInstanceState)
53 | binding.btnDiffutil.click {
54 | adapter.updateList(galaxies)
55 | }
56 | useDiffUtil()
57 | }
58 |
59 |
60 | override fun onDestroyView() {
61 | super.onDestroyView()
62 | _binding = null
63 | }
64 |
65 | private fun useDiffUtil() {
66 | adapter = MyDiffUtilAdapter(stars)
67 | binding.apply {
68 | rvSingleSelectable.adapter = adapter
69 | rvSingleSelectable.setHasFixedSize(true)
70 |
71 | adapter.itemClickListener = { _, name ->
72 | requireContext().toast(name)
73 | }
74 | //adapter.updateList(galaxies)
75 | }
76 | }
77 |
78 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/remindmethere/GeofenceHelper.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.remindmethere
2 |
3 | import android.app.PendingIntent
4 | import android.content.Context
5 | import android.content.ContextWrapper
6 | import android.content.Intent
7 | import android.os.Build
8 | import com.google.android.gms.common.api.ApiException
9 | import com.google.android.gms.location.Geofence
10 | import com.google.android.gms.location.GeofenceStatusCodes
11 | import com.google.android.gms.location.GeofencingRequest
12 | import com.google.android.gms.maps.model.LatLng
13 |
14 | class GeofenceHelper(base: Context?) : ContextWrapper(base) {
15 |
16 | val pendingIntent: PendingIntent by lazy {
17 | val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
18 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
19 | PendingIntent.getBroadcast(this, 2607, intent,
20 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
21 | } else {
22 | PendingIntent.getBroadcast(this, 2607, intent, PendingIntent.FLAG_UPDATE_CURRENT)
23 | }
24 | }
25 |
26 | fun getGeofencingRequest(geofence: Geofence): GeofencingRequest {
27 | return GeofencingRequest.Builder()
28 | .addGeofence(geofence)
29 | .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
30 | .build()
31 | }
32 |
33 | fun getGeofence(id: String, latLng: LatLng, radius: Float): Geofence {
34 | return Geofence.Builder()
35 | .setRequestId(id)
36 | .setCircularRegion(latLng.latitude, latLng.longitude, radius)
37 | .setExpirationDuration(Geofence.NEVER_EXPIRE)
38 | .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT or Geofence.GEOFENCE_TRANSITION_DWELL)
39 | .setLoiteringDelay(5000)
40 | .build()
41 | }
42 |
43 | fun getErrorString(e: Exception): String {
44 | return if (e is ApiException) {
45 | when(e.statusCode) {
46 | GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE -> "GEOFENCE_NOT_AVAILABLE"
47 | GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES -> "GEOFENCE_TOO_MANY_GEOFENCES"
48 | GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS -> "GEOFENCE_TOO_MANY_PENDING_INTENTS"
49 | else -> e.statusCode.toString()
50 | }
51 | } else {
52 | e.localizedMessage
53 | }
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/dialogs/CustomAlertDialog.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension.dialogs
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.annotation.DimenRes
8 | import androidx.annotation.DrawableRes
9 | import androidx.annotation.LayoutRes
10 | import androidx.appcompat.app.AlertDialog
11 | import androidx.core.content.ContextCompat
12 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
13 | import com.mobven.extension.drawable
14 |
15 | class CustomAlertDialog(
16 | val context: Context,
17 | @DimenRes val width: Int = 0,
18 | @DrawableRes val bg: Int = 0,
19 | private val viewHolderCreator: (dialog: CustomAlertDialog) -> View?,
20 | ) {
21 |
22 | private var builder: MaterialAlertDialogBuilder? = null
23 | var cancelable: Boolean = true
24 | var isBackGroundTransparent: Boolean = true
25 |
26 | var dialog: AlertDialog? = null
27 |
28 | fun create(): AlertDialog {
29 | viewHolderCreator(this)?.let {
30 | builder = MaterialAlertDialogBuilder(context).setView(it)
31 | }
32 | dialog = builder?.setCancelable(cancelable)?.create()
33 |
34 | if (isBackGroundTransparent) {
35 | context.apply {
36 | if (bg != 0) {
37 | dialog?.window?.setBackgroundDrawable(
38 | drawable(bg)
39 | )
40 | }
41 | if (width == 0) {
42 | dialog?.window?.setLayout(
43 | ViewGroup.LayoutParams.WRAP_CONTENT,
44 | ViewGroup.LayoutParams.WRAP_CONTENT
45 | )
46 | } else {
47 | dialog?.window?.setLayout(
48 | resources.getDimensionPixelSize(width),
49 | ViewGroup.LayoutParams.WRAP_CONTENT
50 | )
51 | }
52 | }
53 | }
54 |
55 | return dialog!!
56 | }
57 |
58 | fun onCancelListener(func: () -> Unit): MaterialAlertDialogBuilder? =
59 | builder?.setOnCancelListener { func.invoke() }
60 | }
61 |
62 | fun customDialogOf(
63 | context: Context,
64 | @DimenRes width: Int = 0,
65 | @DrawableRes bg: Int = 0,
66 | viewHolderCreator: (dialog: CustomAlertDialog) -> View?,
67 | ) {
68 | CustomAlertDialog(context, width, bg, viewHolderCreator).create().show()
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
16 |
19 |
22 |
25 |
28 |
29 |
34 |
39 |
44 |
48 |
52 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
19 |
20 |
24 |
25 |
26 |
29 |
32 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
55 |
56 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/remindmethere/GeofenceBroadcastReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.remindmethere
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import com.google.android.gms.location.Geofence
8 | import com.google.android.gms.location.GeofenceStatusCodes
9 | import com.google.android.gms.location.GeofencingEvent
10 | import com.mobven.extension.toast
11 |
12 | class GeofenceBroadcastReceiver : BroadcastReceiver() {
13 |
14 | private val TAG = "GeofenceBroadcastReceiv"
15 |
16 | override fun onReceive(context: Context, intent: Intent) {
17 | val notificationHelper = NotificationHelper(context)
18 | val geofencingEvent = GeofencingEvent.fromIntent(intent)
19 |
20 | if (geofencingEvent?.hasError() == true) {
21 | val errorMessage = GeofenceStatusCodes
22 | .getStatusCodeString(geofencingEvent.errorCode)
23 | Log.e(TAG, errorMessage)
24 | return
25 | }
26 |
27 | val geofenceList: List? = geofencingEvent?.triggeringGeofences
28 | geofenceList?.forEach {
29 | Log.d(TAG, "onReceive: ${it.requestId}")
30 | }
31 |
32 | when (geofencingEvent?.geofenceTransition) {
33 | Geofence.GEOFENCE_TRANSITION_ENTER -> {
34 | context.toast("GEOFENCE_TRANSITION_ENTER")
35 | Log.d(TAG, "GEOFENCE_TRANSITION_ENTER")
36 | notificationHelper.sendHighPriorityNotification(
37 | "GEOFENCE_TRANSITION_ENTER",
38 | "",
39 | GeofenceForegroundService::class.java
40 | )
41 | }
42 | Geofence.GEOFENCE_TRANSITION_EXIT -> {
43 | context.toast("GEOFENCE_TRANSITION_EXIT")
44 | Log.d(TAG, "GEOFENCE_TRANSITION_EXIT")
45 | notificationHelper.sendHighPriorityNotification(
46 | "GEOFENCE_TRANSITION_EXIT",
47 | "",
48 | GeofenceForegroundService::class.java
49 | )
50 | }
51 | Geofence.GEOFENCE_TRANSITION_DWELL -> {
52 | context.toast("GEOFENCE_TRANSITION_DWELL")
53 | Log.d(TAG, "GEOFENCE_TRANSITION_DWELL")
54 | notificationHelper.sendHighPriorityNotification(
55 | "GEOFENCE_TRANSITION_DWELL",
56 | "",
57 | GeofenceForegroundService::class.java
58 | )
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/compose/SampleData.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.compose
2 |
3 | object SampleData {
4 | // Sample conversation data
5 | val conversationSample = listOf(
6 | Message(
7 | "Colleague",
8 | "Test...Test...Test..."
9 | ),
10 | Message(
11 | "Colleague",
12 | "List of Android versions:\n" +
13 | "Android KitKat (API 19)\n" +
14 | "Android Lollipop (API 21)\n" +
15 | "Android Marshmallow (API 23)\n" +
16 | "Android Nougat (API 24)\n" +
17 | "Android Oreo (API 26)\n" +
18 | "Android Pie (API 28)\n" +
19 | "Android 10 (API 29)\n" +
20 | "Android 11 (API 30)\n" +
21 | "Android 12 (API 31)\n"
22 | ),
23 | Message(
24 | "Colleague",
25 | "I think Kotlin is my favorite programming language.\n" +
26 | "It's so much fun!"
27 | ),
28 | Message(
29 | "Colleague",
30 | "Searching for alternatives to XML layouts..."
31 | ),
32 | Message(
33 | "Colleague",
34 | "Hey, take a look at Jetpack Compose, it's great!\n" +
35 | "It's the Android's modern toolkit for building native UI." +
36 | "It simplifies and accelerates UI development on Android." +
37 | "Less code, powerful tools, and intuitive Kotlin APIs :)"
38 | ),
39 | Message(
40 | "Colleague",
41 | "It's available from API 21+ :)"
42 | ),
43 | Message(
44 | "Colleague",
45 | "Writing Kotlin for UI seems so natural, Compose where have you been all my life?"
46 | ),
47 | Message(
48 | "Colleague",
49 | "Android Studio next version's name is Arctic Fox"
50 | ),
51 | Message(
52 | "Colleague",
53 | "Android Studio Arctic Fox tooling for Compose is top notch ^_^"
54 | ),
55 | Message(
56 | "Colleague",
57 | "I didn't know you can now run the emulator directly from Android Studio"
58 | ),
59 | Message(
60 | "Colleague",
61 | "Compose Previews are great to check quickly how a composable layout looks like"
62 | ),
63 | Message(
64 | "Colleague",
65 | "Previews are also interactive after enabling the experimental setting"
66 | ),
67 | Message(
68 | "Colleague",
69 | "Have you tried writing build.gradle with KTS?"
70 | ),
71 | )
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/recyclerview/singleselectable/SingleSelectableAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.recyclerview.singleselectable
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.mobven.extension.click
7 | import com.mobven.extension.gone
8 | import com.mobven.extension.show
9 | import com.mobven.extensions.databinding.ItemSelectableBinding
10 |
11 | class SingleSelectableAdapter(private val items: List): RecyclerView.Adapter() {
12 |
13 | /**
14 | * This is for initial selected position
15 | * -1 means no item selected
16 | * if you have default selected position change this value
17 | */
18 | var selectedPos = -1
19 |
20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
21 | val binding = ItemSelectableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
22 | return ViewHolder(binding)
23 | }
24 |
25 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
26 | holder.bind(items[position])
27 | }
28 |
29 | override fun getItemCount(): Int = items.size
30 |
31 | inner class ViewHolder(private val binding: ItemSelectableBinding): RecyclerView.ViewHolder(binding.root) {
32 |
33 | fun bind(item: SelectableModel) {
34 | binding.apply {
35 | cbMaterial.text = item.title
36 |
37 | if (selectedPos == -1) {
38 | item.isSelected = false
39 | imgSelectable.gone()
40 | } else {
41 | item.isSelected = selectedPos == adapterPosition
42 | cbMaterial.isChecked = selectedPos == adapterPosition
43 | if (selectedPos == adapterPosition) {
44 | imgSelectable.show()
45 | } else {
46 | imgSelectable.gone()
47 | }
48 |
49 | }
50 |
51 | itemView.click {
52 | setChecked(selectedPos)
53 | }
54 |
55 | }
56 | }
57 |
58 | private fun setChecked(previousSelectedPos: Int) {
59 | binding.apply {
60 | selectedPos = adapterPosition
61 | items[adapterPosition].isSelected = true
62 | items[adapterPosition].title = "SELECTED Model $adapterPosition"
63 | notifyItemChanged(adapterPosition)
64 | if (previousSelectedPos != -1 && previousSelectedPos != adapterPosition) {
65 | items[previousSelectedPos].isSelected = false
66 | items[previousSelectedPos].title = "Selectable Model $previousSelectedPos"
67 | notifyItemChanged(previousSelectedPos)
68 | }
69 | }
70 | }
71 |
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/remindmethere/NotificationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.remindmethere
2 |
3 | import android.app.Notification
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.app.PendingIntent
7 | import android.content.Context
8 | import android.content.ContextWrapper
9 | import android.content.Intent
10 | import android.graphics.Color
11 | import android.os.Build
12 | import androidx.annotation.RequiresApi
13 | import androidx.core.app.NotificationCompat
14 | import androidx.core.app.NotificationManagerCompat
15 | import com.mobven.extensions.R
16 | import kotlin.random.Random
17 |
18 | class NotificationHelper(base: Context?) : ContextWrapper(base) {
19 | private val CHANNEL_NAME = "High priority channel"
20 | private val CHANNEL_ID = "com.example.notifications$CHANNEL_NAME"
21 |
22 | @RequiresApi(api = Build.VERSION_CODES.O)
23 | private fun createChannels() {
24 | val notificationChannel =
25 | NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH)
26 | notificationChannel.enableLights(true)
27 | notificationChannel.enableVibration(true)
28 | notificationChannel.description = "this is the description of the channel."
29 | notificationChannel.lightColor = Color.RED
30 | notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
31 | val manager: NotificationManager =
32 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
33 | manager.createNotificationChannel(notificationChannel)
34 | }
35 |
36 | fun sendHighPriorityNotification(title: String?, body: String?, activityName: Class<*>?) {
37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
38 | createChannels()
39 | }
40 | val intent = Intent(this, activityName)
41 | val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
42 | PendingIntent.getBroadcast(
43 | this, 267, intent,
44 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
45 | )
46 | } else {
47 | PendingIntent.getBroadcast(this, 267, intent, PendingIntent.FLAG_UPDATE_CURRENT)
48 | }
49 | val notification: Notification =
50 | NotificationCompat.Builder(this, CHANNEL_ID)
51 | .setSmallIcon(R.drawable.ic_launcher_background)
52 | .setPriority(NotificationCompat.PRIORITY_HIGH)
53 | .setStyle(
54 | NotificationCompat.BigTextStyle().setSummaryText("summary")
55 | .setBigContentTitle(title).bigText(body)
56 | )
57 | .setContentIntent(pendingIntent)
58 | .setAutoCancel(true)
59 | .build()
60 | NotificationManagerCompat.from(this).notify(Random.nextInt(), notification)
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MobvenExtensions
3 | CHOOSE VIDEO FROM GALLERY
4 | CHOOSE PHOTO FROM GALLERY
5 |
6 |
7 | - Boolean Ext.
8 | - Collection Ext.
9 | - Context Ext.
10 | - Date Ext.
11 | - Event Ext.
12 | - Int Ext.
13 | - Lifecycle Ext.
14 | - Long Ext.
15 | - String Ext.
16 | - View Ext.
17 | - Single Selectable RecyclerView
18 | - Request Permissions
19 | - Compose Playground
20 | - Compose Layout
21 | - Concat Adapter
22 | - Diff Util RecyclerView
23 | - Geofence
24 | - Custom Shadow
25 | - Flow Examples
26 |
27 | LayoutComposeActivity
28 | Reminder added!
29 | Reminder removed!
30 | Marker
31 | %1$s meters
32 | Where do you want to be reminded?
33 | Choose the radius
34 | Enter the message
35 | Required
36 | Unknown error: the Geofence service is not available now.
37 | Geofence service is not available now. Go to Settings>Location>Mode and choose High accuracy.
38 | Your app has registered too many geofences.
39 | You have provided too many PendingIntents to the addGeofences() call.
40 | MapsActivity
41 | Location Updated: %1$s
42 | Launch activity
43 | Location unknown
44 | Location Updates
45 |
46 | Location permission is needed for core functionality
47 | Permission was denied, but is needed for core
48 | functionality.
49 | Settings
50 | OK
51 | Request location updates
52 | Remove location updates
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Extensify
2 | Most used extension methods for Kotlin
3 |
4 |
5 | # Download
6 |
7 | ### Step 1. Add the JitPack repository to your build file
8 |
9 | ```
10 | allprojects {
11 | repositories {
12 | ...
13 | maven { url 'https://jitpack.io' }
14 | }
15 | }
16 |
17 | ```
18 |
19 | ### Step 2. Add the dependency
20 |
21 | ```
22 | dependencies {
23 | implementation 'com.github.mobven:extensify:1.1.0'
24 | }
25 | ```
26 | ## Dialog
27 |
28 | * fullScreenDialogOf(fm: FragmentManager, dialogTheme: Int, viewHolderCreator: (inflater: LayoutInflater, dialog: FullScreenDialogFragment) -> View?)
29 | * bottomSheetOf(fm: FragmentManager, isFullScreen: Boolean = true, heightMultiplier: Int = 50, viewHolderCreator: (inflater: LayoutInflater, sheet: BottomSheetExposer) -> View?)
30 | * alert { setTitle("Title") setMessage("Message") positiveButton("Positive Button") { } negativeButton("Negative Button") { } }
31 | * customDialogOf(context: Context, @DimenRes width: Int = 0, @DrawableRes bg: Int = 0, viewHolderCreator: (dialog: CustomAlertDialog) -> View?)
32 |
33 | ## View
34 |
35 | * click()
36 | * show()
37 | * gone()
38 | * hideKeyboard()
39 | * showKeyboard()
40 | * showIf(condition: () -> Boolean)
41 | * hideIf(predicate: () -> Boolean)
42 | * removeIf(predicate: () -> Boolean)
43 | * multipleOnClick(vararg view: View, onClick: () -> Unit)
44 | * onTabSelectedListener(onSelected: (TabLayout.Tab?) -> Unit, onReselected: (TabLayout.Tab?) -> Unit, onUnselected: (TabLayout.Tab?) -> Unit)
45 | * PdfRenderer.Page.renderAndClose(width: Int)
46 |
47 |
48 | ## Context
49 |
50 | * toast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT)
51 | * heightPixels()
52 | * widthPixels()
53 | * drawable(res: Int)
54 | * color(color: Int)
55 | * dpToPixels(dp: Float)
56 | * startActivityWithExtras
57 | * showUrlOnCustomTabs(url: String,
58 | shareState: Int = CustomTabsIntent.SHARE_STATE_OFF,
59 | navigationColor: Int = android.R.color.holo_green_dark,
60 | toolbarColor: Int = android.R.color.holo_blue_bright
61 | )
62 | * chooseFromGallery(callback: ActivityResultCallback>): ActivityResultLauncher
63 |
64 | ## Collection
65 |
66 | * MutableList.moveAt(oldIndex: Int, newIndex: Int)
67 | * MutableList.move(item: T, newIndex: Int)
68 |
69 | ## Date
70 |
71 | * formatToViewTime(customFormat: String = "dd MMMM yyyy")
72 |
73 | ## Int
74 |
75 | * orZero()
76 |
77 | ## Double
78 |
79 | * orZero()
80 | * localizedNumberFormat()
81 |
82 | ## Long
83 |
84 | * orZero()
85 |
86 | ## String
87 |
88 | * SpannableString.setColor(color: Int, start: Int, end: Int)
89 | * SpannableString.bold(start: Int, end: Int)
90 | * SpannableString.underline(start: Int, end: Int)
91 | * SpannableString.italic(start: Int, end: Int)
92 | * String.capitalizeWords(locale: Locale, delimiter: String)
93 | * String.getCreditCardIconType()
94 | * String.isValidTCKN()
95 |
96 | ## Boolean
97 |
98 | * orFalse()
99 |
100 | ## Lifecycle
101 |
102 | * LifecycleOwner.observe(liveData: LiveData?, observer: (T) -> Unit)
103 |
104 | ## Additional Features
105 |
106 | * [Event Wrapper](https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
107 |
108 | ---
109 |
110 | Developed with 🖤 at [Mobven](https://mobven.com/)
111 |
112 |
113 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/dialogs/BottomSheetExposer.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension.dialogs
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.FrameLayout
9 | import androidx.fragment.app.FragmentManager
10 | import com.google.android.material.bottomsheet.BottomSheetBehavior
11 | import com.google.android.material.bottomsheet.BottomSheetDialog
12 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
13 | import com.mobven.extension.R
14 | import com.mobven.extension.heightPixels
15 |
16 | class BottomSheetExposer : BottomSheetDialogFragment() {
17 | companion object {
18 |
19 | private var heightMultiplier: Int = 50
20 | private var isWrapContent: Boolean = false
21 | private var dialogTheme: Int = android.R.style.Theme_Material_NoActionBar_TranslucentDecor
22 | private var viewHolderCreator: (inflater: LayoutInflater, sheet: BottomSheetExposer) -> View? = {_, _ -> null}
23 | fun instance(
24 | heightMultiplier: Int,
25 | isWrapContent: Boolean,
26 | dialogTheme: Int,
27 | viewHolderCreator: (inflater: LayoutInflater, sheet: BottomSheetExposer) -> View?
28 | ): BottomSheetExposer {
29 | this.heightMultiplier = heightMultiplier
30 | this.isWrapContent = isWrapContent
31 | this.dialogTheme = dialogTheme
32 | this.viewHolderCreator = viewHolderCreator
33 | return BottomSheetExposer()
34 | }
35 | }
36 |
37 | private fun setupFullHeight(bottomSheetDialog: BottomSheetDialog) {
38 | val bottomSheet =
39 | bottomSheetDialog.findViewById(R.id.design_bottom_sheet) as FrameLayout?
40 | bottomSheet?.let { frame ->
41 | val behavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(frame)
42 | if (isWrapContent.not()) {
43 | frame.layoutParams.height =
44 | requireActivity().heightPixels() * heightMultiplier / 100
45 | }
46 | behavior.state = BottomSheetBehavior.STATE_EXPANDED
47 | behavior.skipCollapsed = true
48 | }
49 | }
50 |
51 | override fun onStart() {
52 | super.onStart()
53 | val sheetContainer = requireView().parent as ViewGroup
54 | if (isWrapContent.not()) {
55 | sheetContainer.layoutParams.height =
56 | requireActivity().heightPixels() * heightMultiplier / 100
57 | }
58 |
59 | }
60 |
61 | override fun onCreateView(
62 | inflater: LayoutInflater,
63 | container: ViewGroup?,
64 | savedInstanceState: Bundle?
65 | ): View? {
66 | return viewHolderCreator(inflater, this)
67 | }
68 |
69 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
70 | val dialog = BottomSheetDialog(requireContext(), dialogTheme)
71 | dialog.setOnShowListener {
72 | val bottomSheetDialog = it as BottomSheetDialog
73 | setupFullHeight(bottomSheetDialog)
74 | }
75 | return dialog
76 | }
77 | }
78 |
79 | fun bottomSheetOf(
80 | fragmentManager: FragmentManager,
81 | heightMultiplier: Int = 50,
82 | isWrapContent: Boolean = false,
83 | tag: String = "BOTTOM_SHEET",
84 | dialogTheme: Int = android.R.style.Theme_Material_NoActionBar_TranslucentDecor,
85 | viewHolderCreator: (inflater: LayoutInflater, sheet: BottomSheetExposer) -> View?
86 | ) {
87 | BottomSheetExposer.instance(
88 | heightMultiplier,
89 | isWrapContent,
90 | dialogTheme,
91 | viewHolderCreator
92 | ).show(fragmentManager, tag)
93 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/MenuFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions
2 |
3 | import android.Manifest
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.widget.Toast
11 | import androidx.activity.result.ActivityResultLauncher
12 | import androidx.fragment.app.Fragment
13 | import androidx.navigation.fragment.findNavController
14 | import com.mobven.extension.requestPermissions
15 | import com.mobven.extension.toast
16 | import com.mobven.extensions.compose.ComposePlaygroundActivity
17 | import com.mobven.extensions.compose.layout.LayoutComposeActivity
18 | import com.mobven.extensions.customshadow.CustomShadowActivity
19 | import com.mobven.extensions.databinding.FragmentMenuBinding
20 | import com.mobven.extensions.recyclerview.concatadapter.ConcatExampleActivity
21 | import com.mobven.extensions.remindmethere.MapsActivity
22 |
23 | class MenuFragment : Fragment() {
24 |
25 | private var _binding: FragmentMenuBinding? = null
26 | private val binding get() = _binding!!
27 | private lateinit var permissionChecker: ActivityResultLauncher>
28 |
29 | override fun onCreateView(
30 | inflater: LayoutInflater,
31 | container: ViewGroup?,
32 | savedInstanceState: Bundle?
33 | ): View {
34 | _binding = FragmentMenuBinding.inflate(inflater, container, false)
35 | return binding.root
36 | }
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | permissionChecker = requireActivity().requestPermissions { permissions ->
41 | permissions?.entries?.forEach {
42 | Log.d("Permissions","${it.key} is ${it.value}")
43 | context.toast("${it.key} is ${it.value}", Toast.LENGTH_LONG)
44 | }
45 | }
46 | }
47 |
48 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
49 | super.onViewCreated(view, savedInstanceState)
50 | initMenu()
51 | }
52 |
53 | private fun initMenu() {
54 | binding.rvMenu.adapter =
55 | MenuAdapter(resources.getStringArray(R.array.menu_array).toMutableList()).apply {
56 | menuClick = { navigateToMenu(it) }
57 | }
58 | }
59 |
60 | override fun onDestroyView() {
61 | super.onDestroyView()
62 | _binding = null
63 | }
64 |
65 | private fun navigateToMenu(menuStr: String) {
66 | when(menuStr) {
67 | Menu.SINGLE_SELECT_LIST -> findNavController().navigate(R.id.action_menuFragment_to_singleSelectableRecyclerView)
68 | Menu.DIFF_UTIL_LIST -> findNavController().navigate(R.id.action_menuFragment_to_diffUtilRecyclerView)
69 | Menu.VIEW_EXT -> findNavController().navigate(R.id.action_menuFragment_to_viewExtDemoActivity)
70 | Menu.REQUEST_PERMISSIONS -> permissionChecker.launch(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE))
71 | Menu.COMPOSE_PLAYGROUND -> startActivity(Intent(requireContext(), ComposePlaygroundActivity::class.java))
72 | Menu.LAYOUT_COMPOSE -> startActivity(Intent(requireContext(), LayoutComposeActivity::class.java))
73 | Menu.CONCAT_ADAPTER -> startActivity(Intent(requireContext(), ConcatExampleActivity::class.java))
74 | Menu.GEOFENCE -> startActivity(Intent(requireContext(), MapsActivity::class.java))
75 | Menu.CUSTOM_SHADOW -> startActivity(Intent(requireContext(), CustomShadowActivity::class.java))
76 | Menu.FLOW_EXAMPLE -> findNavController().navigate(R.id.action_menuFragment_to_flowFragment)
77 | }
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/String.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import android.graphics.Typeface
4 | import android.text.SpannableString
5 | import android.text.style.ForegroundColorSpan
6 | import android.text.style.StyleSpan
7 | import android.text.style.UnderlineSpan
8 | import com.mobven.extension.definitions.CreditCardType
9 | import java.util.*
10 |
11 | /**
12 | * Extension method for change color of spannable string
13 | */
14 | fun SpannableString.setColor(color: Int, start: Int, end: Int): SpannableString {
15 | this.setSpan(ForegroundColorSpan(color), start, end, 0)
16 | return this
17 | }
18 |
19 | /**
20 | * Extension method for change typeface of spannable string
21 | */
22 | fun SpannableString.bold(start: Int, end: Int): SpannableString {
23 | this.setSpan(StyleSpan(Typeface.BOLD), start, end, 0)
24 | return this
25 | }
26 |
27 | /**
28 | * Extension method for change typeface of spannable string
29 | */
30 | fun SpannableString.underline(start: Int, end: Int): SpannableString {
31 | this.setSpan(UnderlineSpan(), start, end, 0)
32 | return this
33 | }
34 |
35 | /**
36 | * Extension method for change typeface of spannable string
37 | */
38 | fun SpannableString.italic(start: Int, end: Int): SpannableString {
39 | this.setSpan(StyleSpan(Typeface.ITALIC), start, end, 0)
40 | return this
41 | }
42 |
43 | /**
44 | * Extension method for capitalize all of the words in a [String]
45 | * @param locale is to determine language if word has a different character set from app
46 | * @param delimiter is to determine delimiter for each word
47 | * @sample "çay erdal bakkalda içilir".capitalizeWords(Locale.forLanguageTag("TR")) returns "Çay Erdal Bakkalda İçilir"
48 | * @sample "çay-şeker".capitalizeWords(Locale.forLanguageTag("TR"), "-") returns "Çay-Şeker"
49 | */
50 | fun String.capitalizeWords(locale: Locale = Locale.ROOT, delimiter: String = " "): String {
51 | return split(delimiter).joinToString(delimiter) { word ->
52 | word.lowercase(locale)
53 | .replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }
54 | }
55 | }
56 |
57 | /**
58 | *
59 | * Get the credit card type of given card number
60 | *
61 | */
62 | fun String.getCreditCardIconType(): String {
63 | return when {
64 | matches(CreditCardType.AMEX.toRegex()) -> {
65 | CreditCardType.AMEX
66 | }
67 | matches(CreditCardType.MAESTRO.toRegex()) -> {
68 | CreditCardType.MAESTRO
69 | }
70 | matches(CreditCardType.VISA.toRegex()) -> {
71 | CreditCardType.VISA
72 | }
73 | matches(CreditCardType.TROY.toRegex()) -> {
74 | CreditCardType.TROY
75 | }
76 | matches(CreditCardType.MASTERCARD.toRegex()) -> {
77 | CreditCardType.MASTERCARD
78 | }
79 | else -> {
80 | CreditCardType.DEFAULT
81 | }
82 | }
83 |
84 | }
85 |
86 | /**
87 | * TC. kimlik numaraları 11 basamaklıdır ve ilk rakam 0 olamaz.
88 | * 1,3,5,7 ve 9.cu hanelerin toplamının 7 ile çarpımından 2,4,6, ve 8. haneler çıkartıldığında geriye kalan sayının 10'a göre modu 10. haneyi verir.
89 | * 1,2,3,4,5,6,7,8,9 ve 10. sayıların toplamının 10'a göre modu 11. rakamı verir.
90 | */
91 | fun String?.isValidTCKN(): Boolean =
92 | this?.takeIf { length == 11 && get(0) != '0' }?.let {
93 | var oddSum = 0
94 | var evenSum = 0
95 | for (i in 0..8) {
96 | if (i % 2 == 0) oddSum += get(i).digitToInt()
97 | else evenSum += get(i).digitToInt()
98 | }
99 | val controlDigit = (oddSum * 7 - evenSum) % 10
100 | if (get(9).digitToInt() != controlDigit) {
101 | return false
102 | }
103 | return get(10).digitToInt() == (controlDigit + evenSum + oddSum) % 10
104 | } ?: run { false }
105 |
106 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
6 | id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.22'
7 | }
8 |
9 | android {
10 | compileSdkVersion 33
11 |
12 | defaultConfig {
13 | applicationId "com.mobven.extensions"
14 | minSdkVersion 21
15 | targetSdkVersion 33
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | vectorDrawables {
21 | useSupportLibrary true
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_17
33 | targetCompatibility JavaVersion.VERSION_17
34 | }
35 | kotlinOptions {
36 | jvmTarget = '17'
37 | }
38 | buildFeatures {
39 | viewBinding true
40 | compose true
41 | }
42 | composeOptions {
43 | kotlinCompilerExtensionVersion = "1.4.8"
44 | }
45 | packagingOptions {
46 | resources {
47 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
48 | }
49 | }
50 | namespace 'com.mobven.extensions'
51 | }
52 |
53 | dependencies {
54 |
55 | implementation project(":extension")
56 |
57 | implementation 'androidx.core:core-ktx:1.9.0'
58 | implementation 'androidx.appcompat:appcompat:1.5.1'
59 | implementation 'com.google.android.material:material:1.6.1'
60 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
61 | //Navigation
62 | implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0-alpha02'
63 | implementation 'androidx.navigation:navigation-ui-ktx:2.6.0-alpha02'
64 |
65 | // Integration with activities
66 | implementation 'androidx.activity:activity-compose:1.6.0'
67 | // Compose Material Design
68 | implementation 'androidx.compose.material:material:1.2.1'
69 | // Tooling support (Previews, etc.)
70 | implementation 'androidx.compose.ui:ui-tooling:1.2.1'
71 | implementation "androidx.compose.ui:ui:1.2.1"
72 | implementation "androidx.compose.ui:ui-tooling-preview:1.2.1"
73 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
74 | implementation 'io.coil-kt:coil-compose:2.2.0'
75 |
76 | implementation "androidx.recyclerview:recyclerview:1.3.0-rc01"
77 | implementation 'androidx.databinding:databinding-runtime:7.3.1'
78 | implementation "androidx.core:core-splashscreen:1.0.0"
79 |
80 | //Coroutines
81 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
82 |
83 | //Fuel
84 | implementation "com.github.kittinunf.fuel:fuel-android:2.3.1"
85 |
86 | implementation 'com.google.android.gms:play-services-maps:18.1.0'
87 | implementation 'com.google.android.gms:play-services-location:20.0.0'
88 |
89 | implementation 'com.google.code.gson:gson:2.9.0'
90 |
91 | implementation "androidx.work:work-runtime-ktx:2.7.1"
92 |
93 | implementation 'com.github.fondesa:kpermissions:3.3.0'
94 |
95 | testImplementation 'junit:junit:4.13.2'
96 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
97 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
98 | implementation 'com.github.bumptech.glide:glide:4.13.2'
99 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.2.1"
100 | kapt 'com.github.bumptech.glide:compiler:4.13.2'
101 |
102 | implementation ("com.github.javadev:underscore:1.90")
103 |
104 | implementation("com.ryanharter.kotlinx.serialization:kotlinx-serialization-xml:0.0.1-SNAPSHOT")
105 | implementation group: 'org.simpleframework', name: 'simple-xml', version: '2.7.1'
106 |
107 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/flow/FlowViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.flow
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import kotlinx.coroutines.delay
6 | import kotlinx.coroutines.flow.*
7 |
8 | class FlowViewModel : ViewModel() {
9 |
10 | private fun api1() = flow {
11 | delay(5000)
12 | emit("API 1 Response")
13 | }
14 |
15 | private fun api2() = flow {
16 | delay(3000)
17 | emit("API 2 Response")
18 | }
19 |
20 | private fun api3() = flow {
21 | delay(1000)
22 | emit("API 3 Response")
23 | }
24 |
25 | private suspend fun api3Suspend(): String {
26 | delay(1000)
27 | return "API 3 Response"
28 | }
29 |
30 | private fun callApiSynchronously() {
31 | var startTime = 0L
32 | var isLoading = false
33 |
34 | api1().onStart {
35 | startTime = System.currentTimeMillis()
36 | isLoading = true
37 | println("Loading: $isLoading")
38 | println("APIs Called ->")
39 | }.onEach {
40 | println(it)
41 | }.onCompletion {
42 | api2().onEach {
43 | println(it)
44 | }.retry(3000) {
45 | println("Api 2 Retry")
46 | true
47 | }.catch {
48 | println("Api 2 Catch")
49 | }.onCompletion {
50 | api3().onEach {
51 | println(it)
52 | }.onCompletion {
53 | println("APIs Complete: ${System.currentTimeMillis() - startTime}ms")
54 | println("Loading: $isLoading")
55 | }.launchIn(viewModelScope)
56 | }.launchIn(viewModelScope)
57 | }.retry(3000) {
58 | println(it.message)
59 | false
60 | }.catch {
61 | println(it.message)
62 | }.launchIn(viewModelScope)
63 | }
64 |
65 | private fun callApiWithMerge() {
66 | var startTime = 0L
67 | var isLoading = false
68 | val mergedApiCall = merge(api1(), api2(), api3())
69 | mergedApiCall.onStart {
70 | isLoading = true
71 | println("Loading: $isLoading")
72 | startTime = System.currentTimeMillis()
73 | println("Merged APIs Called ->")
74 | }.onEach {
75 | println(it)
76 | }.onCompletion {
77 | println("Merged APIs Complete: ${System.currentTimeMillis() - startTime}ms")
78 | println("Loading: $isLoading")
79 | }.launchIn(viewModelScope)
80 | }
81 |
82 | private fun callApiWithZip() {
83 | val zippedCall = api1().zip(api2()) { api1Value, api2Value ->
84 | println(api1Value)
85 | //println(api2Value)
86 | }
87 | var startTime = 0L
88 | var isLoading = false
89 | zippedCall.onStart {
90 | isLoading = true
91 | println("Loading: $isLoading")
92 | startTime = System.currentTimeMillis()
93 | println("Merged APIs Called ->")
94 | }.onCompletion {
95 | println("Merged APIs Complete: ${System.currentTimeMillis() - startTime}ms")
96 | println("Loading: $isLoading")
97 | }.launchIn(viewModelScope)
98 | }
99 |
100 | private fun callApiWithFlattenMerge() {
101 | var startTime = 0L
102 | flowOf(api2(), api1(), api3()).flattenMerge()
103 | .onStart {
104 | println("Loading: True")
105 | startTime = System.currentTimeMillis()
106 | }
107 | .onEach {
108 | println("$it ${System.currentTimeMillis() - startTime}ms")
109 | }.onCompletion {
110 | println("FlattenMerge APIs Complete: ${System.currentTimeMillis() - startTime}ms")
111 | println("Loading: False")
112 | }.launchIn(viewModelScope)
113 | }
114 |
115 | init {
116 | //callApiWithFlattenMerge()
117 | //callApiSynchronously()
118 | callApiWithMerge()
119 | //callApiWithZip()
120 | }
121 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/remindmethere/GeofenceForegroundService.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.remindmethere
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.*
5 | import android.content.Intent
6 | import android.content.pm.ServiceInfo
7 | import android.os.Build
8 | import android.os.IBinder
9 | import android.os.Looper
10 | import android.util.Log
11 | import androidx.core.app.NotificationCompat
12 | import com.google.android.gms.location.*
13 | import com.google.android.gms.maps.model.LatLng
14 | import com.mobven.extensions.R
15 |
16 | class GeofenceForegroundService: Service() {
17 |
18 | private lateinit var geofenceHelper: GeofenceHelper
19 | private lateinit var geofencingClient: GeofencingClient
20 | private lateinit var pendingIntent: PendingIntent
21 | private lateinit var locationClient: FusedLocationProviderClient
22 | private lateinit var locationCallback: LocationCallback
23 | private val geofenceId = "Some_Geofence_Id"
24 | private val geofenceRadius = 200.0
25 |
26 | private val TAG = "GeofenceForegroundServi"
27 |
28 | override fun onBind(p0: Intent?): IBinder? {
29 | return null
30 | }
31 |
32 | @SuppressLint("MissingPermission")
33 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
34 | geofenceHelper = GeofenceHelper(this)
35 |
36 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
37 | val channel =
38 | NotificationChannel("Geofence", "Geofence Loc", NotificationManager.IMPORTANCE_NONE)
39 | val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
40 | manager.createNotificationChannel(channel)
41 | }
42 |
43 | val notification: Notification = NotificationCompat.Builder(this, "Geofence")
44 | .setContentTitle("Geofence Active")
45 | .setContentText("Location Updating...")
46 | .setSmallIcon(R.mipmap.ic_launcher)
47 | .setContentIntent(geofenceHelper.pendingIntent)
48 | .build()
49 |
50 | val latLng = intent?.getParcelableExtra("LATLNG")
51 | geofencingClient = LocationServices.getGeofencingClient(this)
52 | locationClient = LocationServices.getFusedLocationProviderClient(this)
53 | val geofence = geofenceHelper.getGeofence(geofenceId, latLng!!, geofenceRadius.toFloat())
54 | val geofencingRequest = geofenceHelper.getGeofencingRequest(geofence)
55 | pendingIntent = geofenceHelper.pendingIntent
56 | val locationRequest = LocationRequest.create().apply {
57 | interval = 10000
58 | fastestInterval = 5000
59 | priority = LocationRequest.PRIORITY_HIGH_ACCURACY
60 |
61 | }
62 | locationCallback = object : LocationCallback() {
63 | override fun onLocationResult(locationResult: LocationResult) {
64 | super.onLocationResult(locationResult)
65 | Log.d(TAG, "${locationResult.lastLocation?.latitude} ${locationResult.lastLocation?.longitude}")
66 | }
67 | }
68 | Looper.myLooper()?.let {
69 | locationClient.requestLocationUpdates(locationRequest, locationCallback,
70 | it
71 | )
72 | }
73 |
74 | geofencingClient.addGeofences(geofencingRequest, pendingIntent).run {
75 | addOnSuccessListener {
76 | Log.d(TAG, "onSuccess: Geofence Added...")
77 |
78 | }
79 | addOnFailureListener {
80 | Log.d(TAG, "onFailure ${geofenceHelper.getErrorString(it)}")
81 | }
82 | }
83 |
84 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
85 | startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
86 | } else {
87 | startForeground(1, notification)
88 | }
89 |
90 | return START_STICKY
91 | }
92 |
93 | override fun onDestroy() {
94 | Log.i(TAG, "onDestroy: RUN")
95 | geofencingClient.removeGeofences(pendingIntent)
96 | locationClient.removeLocationUpdates(locationCallback)
97 | super.onDestroy()
98 | }
99 |
100 | override fun onTaskRemoved(rootIntent: Intent?) {
101 | Log.i(TAG, "onTaskRemoved: RUN")
102 | super.onTaskRemoved(rootIntent)
103 | stopSelf()
104 | }
105 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/android,androidstudio
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=android,androidstudio
4 |
5 | ### Android ###
6 | # Built application files
7 | *.apk
8 | *.aar
9 | *.ap_
10 | *.aab
11 |
12 | # Files for the ART/Dalvik VM
13 | *.dex
14 |
15 | # Java class files
16 | *.class
17 |
18 | # Generated files
19 | bin/
20 | gen/
21 | out/
22 | # Uncomment the following line in case you need and you don't have the release build type files in your app
23 | # release/
24 |
25 | # Gradle files
26 | .gradle/
27 | build/
28 |
29 | # Local configuration file (sdk path, etc)
30 | local.properties
31 |
32 | # Proguard folder generated by Eclipse
33 | proguard/
34 |
35 | # Log Files
36 | *.log
37 |
38 | # Android Studio Navigation editor temp files
39 | .navigation/
40 |
41 | # Android Studio captures folder
42 | captures/
43 |
44 | # IntelliJ
45 | *.iml
46 | .idea/workspace.xml
47 | .idea/tasks.xml
48 | .idea/gradle.xml
49 | .idea/assetWizardSettings.xml
50 | .idea/dictionaries
51 | .idea/libraries
52 | .idea/codeStyles/Project.xml
53 | # Android Studio 3 in .gitignore file.
54 | .idea/caches
55 | .idea/modules.xml
56 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
57 | .idea/navEditor.xml
58 |
59 | # Keystore files
60 | # Uncomment the following lines if you do not want to check your keystore files in.
61 | #*.jks
62 | #*.keystore
63 |
64 | # External native build folder generated in Android Studio 2.2 and later
65 | .externalNativeBuild
66 | .cxx/
67 |
68 | # Google Services (e.g. APIs or Firebase)
69 | # google-services.json
70 |
71 | # Freeline
72 | freeline.py
73 | freeline/
74 | freeline_project_description.json
75 |
76 | # fastlane
77 | fastlane/report.xml
78 | fastlane/Preview.html
79 | fastlane/screenshots
80 | fastlane/test_output
81 | fastlane/readme.md
82 |
83 | # Version control
84 | vcs.xml
85 |
86 | # lint
87 | lint/intermediates/
88 | lint/generated/
89 | lint/outputs/
90 | lint/tmp/
91 | # lint/reports/
92 |
93 | ### Android Patch ###
94 | gen-external-apklibs
95 | output.json
96 |
97 | # Replacement of .externalNativeBuild directories introduced
98 | # with Android Studio 3.5.
99 |
100 | ### AndroidStudio ###
101 | # Covers files to be ignored for android development using Android Studio.
102 |
103 | # Built application files
104 |
105 | # Files for the ART/Dalvik VM
106 |
107 | # Java class files
108 |
109 | # Generated files
110 |
111 | # Gradle files
112 | .gradle
113 |
114 | # Signing files
115 | .signing/
116 |
117 | # Local configuration file (sdk path, etc)
118 |
119 | # Proguard folder generated by Eclipse
120 |
121 | # Log Files
122 |
123 | # Android Studio
124 | /*/build/
125 | /*/local.properties
126 | /*/out
127 | /*/*/build
128 | /*/*/production
129 | *.ipr
130 | *~
131 | *.swp
132 |
133 | # Android Patch
134 |
135 | # External native build folder generated in Android Studio 2.2 and later
136 |
137 | # NDK
138 | obj/
139 |
140 | # IntelliJ IDEA
141 | *.iws
142 | /out/
143 |
144 | # User-specific configurations
145 | .idea/caches/
146 | .idea/libraries/
147 | .idea/shelf/
148 | .idea/.name
149 | .idea/compiler.xml
150 | .idea/copyright/profiles_settings.xml
151 | .idea/encodings.xml
152 | .idea/misc.xml
153 | .idea/scopes/scope_settings.xml
154 | .idea/vcs.xml
155 | .idea/jsLibraryMappings.xml
156 | .idea/datasources.xml
157 | .idea/dataSources.ids
158 | .idea/sqlDataSources.xml
159 | .idea/dynamic.xml
160 | .idea/uiDesigner.xml
161 |
162 | # OS-specific files
163 | .DS_Store
164 | .DS_Store?
165 | ._*
166 | .Spotlight-V100
167 | .Trashes
168 | ehthumbs.db
169 | Thumbs.db
170 |
171 | # Legacy Eclipse project files
172 | .classpath
173 | .project
174 | .cproject
175 | .settings/
176 |
177 | # Mobile Tools for Java (J2ME)
178 | .mtj.tmp/
179 |
180 | # Package Files #
181 | *.war
182 | *.ear
183 |
184 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
185 | hs_err_pid*
186 |
187 | ## Plugin-specific files:
188 |
189 | # mpeltonen/sbt-idea plugin
190 | .idea_modules/
191 |
192 | # JIRA plugin
193 | atlassian-ide-plugin.xml
194 |
195 | # Mongo Explorer plugin
196 | .idea/mongoSettings.xml
197 |
198 | # Crashlytics plugin (for Android Studio and IntelliJ)
199 | com_crashlytics_export_strings.xml
200 | crashlytics.properties
201 | crashlytics-build.properties
202 | fabric.properties
203 |
204 | ### AndroidStudio Patch ###
205 |
206 | !/gradle/wrapper/gradle-wrapper.jar
207 |
208 | # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio
209 | version.properties
210 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/compose/ComposePlaygroundActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.compose
2 |
3 | import android.content.res.Configuration
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.animation.animateColorAsState
8 | import androidx.compose.animation.animateContentSize
9 | import androidx.compose.foundation.Image
10 | import androidx.compose.foundation.background
11 | import androidx.compose.foundation.border
12 | import androidx.compose.foundation.clickable
13 | import androidx.compose.foundation.layout.*
14 | import androidx.compose.foundation.lazy.LazyColumn
15 | import androidx.compose.foundation.lazy.items
16 | import androidx.compose.foundation.shape.CircleShape
17 | import androidx.compose.material.MaterialTheme
18 | import androidx.compose.material.Surface
19 | import androidx.compose.material.Text
20 | import androidx.compose.runtime.*
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.draw.clip
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.res.painterResource
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import androidx.compose.ui.unit.dp
27 | import com.mobven.extensions.R
28 |
29 | class ComposePlaygroundActivity : ComponentActivity() {
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | setContent {
34 | MaterialTheme {
35 | Conversation(SampleData.conversationSample)
36 | }
37 | }
38 | }
39 | }
40 |
41 | data class Message(val author: String, val body: String)
42 |
43 | @Composable
44 | fun MessageCard(msg: Message) {
45 | Row(modifier = Modifier.padding(all = 8.dp)) {
46 | Image(
47 | painter = painterResource(R.drawable.ic_launcher_foreground),
48 | contentDescription = "Contact profile picture",
49 | modifier = Modifier
50 | .size(40.dp)
51 | .clip(CircleShape)
52 | .background(Color.Cyan)
53 | .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
54 | )
55 |
56 | // Add a horizontal space between the image and the column
57 | Spacer(modifier = Modifier.width(8.dp))
58 |
59 | // We keep track if the message is expanded or not in this
60 | // variable
61 | var isExpanded by remember {
62 | mutableStateOf(false)
63 | }
64 | // surfaceColor will be updated gradually from one color to the other
65 | val surfaceColor: Color by animateColorAsState(
66 | if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
67 | )
68 |
69 | // We toggle the isExpanded variable when we click on this Column
70 | Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
71 | Text(
72 | text = msg.author,
73 | color = MaterialTheme.colors.secondaryVariant,
74 | style = MaterialTheme.typography.subtitle2
75 | )
76 | Spacer(modifier = Modifier.height(4.dp))
77 | Surface(shape = MaterialTheme.shapes.medium,
78 | elevation = 1.dp,
79 | // surfaceColor color will be changing gradually from primary to surface
80 | color = surfaceColor,
81 | // animateContentSize will change the Surface size gradually
82 | modifier = Modifier.animateContentSize().padding(1.dp)
83 | ) {
84 | Text(
85 | text = msg.body,
86 | modifier = Modifier.padding(all = 4.dp),
87 | // If the message is expanded, we display all its content
88 | // otherwise we only display the first line
89 | maxLines = if (isExpanded) Int.MAX_VALUE else 1,
90 | style = MaterialTheme.typography.body2
91 | )
92 | }
93 | }
94 | }
95 | }
96 |
97 | @Composable
98 | fun Conversation(messages: List) {
99 | LazyColumn {
100 | items(messages) { messages ->
101 | MessageCard(msg = messages)
102 | }
103 | }
104 | }
105 |
106 | @Preview(
107 | uiMode = Configuration.UI_MODE_NIGHT_YES,
108 | showBackground = true,
109 | name = "Dark Mode"
110 | )
111 | @Composable
112 | fun PreviewMessageCard() {
113 | MaterialTheme {
114 | Conversation(SampleData.conversationSample)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/remindmethere/MapsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.remindmethere
2 |
3 | import android.Manifest
4 | import android.annotation.SuppressLint
5 | import android.content.Intent
6 | import android.graphics.Color
7 | import android.os.Build
8 | import android.os.Bundle
9 | import android.util.Log
10 | import androidx.appcompat.app.AppCompatActivity
11 | import androidx.core.content.ContextCompat
12 | import com.fondesa.kpermissions.allGranted
13 | import com.fondesa.kpermissions.extension.permissionsBuilder
14 | import com.fondesa.kpermissions.extension.send
15 | import com.google.android.gms.location.GeofencingClient
16 | import com.google.android.gms.location.LocationServices
17 | import com.google.android.gms.maps.CameraUpdateFactory
18 | import com.google.android.gms.maps.GoogleMap
19 | import com.google.android.gms.maps.OnMapReadyCallback
20 | import com.google.android.gms.maps.SupportMapFragment
21 | import com.google.android.gms.maps.model.CircleOptions
22 | import com.google.android.gms.maps.model.LatLng
23 | import com.google.android.gms.maps.model.MarkerOptions
24 | import com.mobven.extension.hasPermissions
25 | import com.mobven.extensions.R
26 | import com.mobven.extensions.databinding.ActivityMapsBinding
27 |
28 | class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
29 |
30 | private lateinit var mMap: GoogleMap
31 | private lateinit var binding: ActivityMapsBinding
32 | private val geofenceRadius = 200.0
33 |
34 | @SuppressLint("MissingPermission")
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 |
38 | binding = ActivityMapsBinding.inflate(layoutInflater)
39 | setContentView(binding.root)
40 |
41 | // Obtain the SupportMapFragment and get notified when the map is ready to be used.
42 | val mapFragment = supportFragmentManager
43 | .findFragmentById(R.id.map) as SupportMapFragment
44 | mapFragment.getMapAsync(this)
45 | }
46 |
47 | override fun onMapReady(googleMap: GoogleMap) {
48 | mMap = googleMap
49 |
50 | // Add a marker in Sydney and move the camera
51 | val bim = LatLng(41.07187, 28.93595)
52 | mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(bim, 16F))
53 |
54 | enableUserLocation()
55 |
56 | mMap.setOnMapLongClickListener {
57 | if (Build.VERSION.SDK_INT >= 29) {
58 | if (hasPermissions(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
59 | tryAddingGeofence(it)
60 | } else {
61 | permissionsBuilder(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
62 | .build().send { result ->
63 | if (result.allGranted()) {
64 | tryAddingGeofence(it)
65 | }
66 | }
67 | }
68 | } else {
69 | tryAddingGeofence(it)
70 | }
71 | }
72 | }
73 |
74 | private fun tryAddingGeofence(latLng: LatLng) {
75 | mMap.clear()
76 | addMarker(latLng)
77 | addCircle(latLng, geofenceRadius)
78 | addGeofence(latLng)
79 | }
80 |
81 | @SuppressLint("MissingPermission")
82 | private fun addGeofence(latLng: LatLng) {
83 | startForegroundService(
84 | Intent(this@MapsActivity, GeofenceForegroundService::class.java).apply {
85 | putExtra("LATLNG", latLng)
86 | }
87 | )
88 | }
89 |
90 | @SuppressLint("MissingPermission")
91 | private fun enableUserLocation() {
92 | permissionsBuilder(Manifest.permission.ACCESS_FINE_LOCATION)
93 | .build().send { result ->
94 | if (result.allGranted()) {
95 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
96 | permissionsBuilder(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
97 | .build().send { resultBackgroundPermission ->
98 | if (resultBackgroundPermission.allGranted()) {
99 | mMap.isMyLocationEnabled = true
100 | }
101 | }
102 | } else {
103 | mMap.isMyLocationEnabled = true
104 | }
105 | }
106 | }
107 | }
108 |
109 | private fun addMarker(latLng: LatLng) {
110 | MarkerOptions().position(latLng).apply {
111 | mMap.addMarker(this)
112 | }
113 | }
114 |
115 | private fun addCircle(latLng: LatLng, radius: Double) {
116 | CircleOptions().apply {
117 | center(latLng)
118 | radius(radius)
119 | strokeColor(Color.argb(255, 255, 0, 0))
120 | fillColor(Color.argb(64, 255, 0, 0))
121 | strokeWidth(4F)
122 | mMap.addCircle(this)
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/compose/layout/LayoutComposeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions.compose.layout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.Image
7 | import androidx.compose.foundation.background
8 | import androidx.compose.foundation.clickable
9 | import androidx.compose.foundation.layout.*
10 | import androidx.compose.foundation.lazy.LazyColumn
11 | import androidx.compose.foundation.lazy.rememberLazyListState
12 | import androidx.compose.foundation.shape.CircleShape
13 | import androidx.compose.foundation.shape.RoundedCornerShape
14 | import androidx.compose.material.*
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.filled.Favorite
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.CompositionLocalProvider
19 | import androidx.compose.runtime.rememberCoroutineScope
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.draw.clip
23 | import androidx.compose.ui.text.font.FontWeight
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import androidx.compose.ui.unit.dp
26 | import coil.compose.rememberImagePainter
27 | import com.mobven.extensions.compose.layout.ui.theme.MobvenExtensionsTheme
28 | import kotlinx.coroutines.launch
29 |
30 | class LayoutComposeActivity : ComponentActivity() {
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | setContent {
34 | MobvenExtensionsTheme {
35 | LayoutsCodelab()
36 | }
37 | }
38 | }
39 | }
40 |
41 | @Composable
42 | fun LayoutsCodelab() {
43 | Scaffold(
44 | topBar = {
45 | TopAppBar(
46 | title = {
47 | Text(text = "LayoutsCodelab")
48 | },
49 | actions = {
50 | IconButton(onClick = { /* doSomething() */ }) {
51 | Icon(Icons.Filled.Favorite, contentDescription = null)
52 | }
53 | }
54 | )
55 | }
56 | ) { innerPadding ->
57 | BodyContent(
58 | Modifier
59 | .padding(innerPadding)
60 | .padding(8.dp))
61 | }
62 | }
63 |
64 | @Composable
65 | fun ImageListItem(index: Int) {
66 | Row(verticalAlignment = Alignment.CenterVertically) {
67 | Image(
68 | painter = rememberImagePainter(
69 | data = "https://developer.android.com/images/brand/Android_Robot.png"
70 | ),
71 | contentDescription = "Android Logo",
72 | modifier = Modifier.size(50.dp)
73 | )
74 | Spacer(Modifier.width(10.dp))
75 | Text("Item #$index", style = MaterialTheme.typography.subtitle1)
76 | }
77 | }
78 |
79 | @Composable
80 | fun ScrollingList() {
81 | val listSize = 100
82 | // We save the scrolling position with this state
83 | val scrollState = rememberLazyListState()
84 | // We save the coroutine scope where our animated scroll will be executed
85 | val coroutineScope = rememberCoroutineScope()
86 |
87 | Column {
88 | Row {
89 | Button(onClick = {
90 | coroutineScope.launch {
91 | // 0 is the first item index
92 | scrollState.animateScrollToItem(0)
93 | }
94 | }) {
95 | Text("Scroll to the top")
96 | }
97 |
98 | Button(onClick = {
99 | coroutineScope.launch {
100 | // listSize - 1 is the last index of the list
101 | scrollState.animateScrollToItem(listSize - 1)
102 | }
103 | }) {
104 | Text("Scroll to the end")
105 | }
106 | }
107 |
108 | LazyColumn(state = scrollState) {
109 | items(listSize) {
110 | ImageListItem(it)
111 | }
112 | }
113 | }
114 | }
115 |
116 |
117 |
118 |
119 | @Composable
120 | fun BodyContent(modifier: Modifier = Modifier) {
121 | Column(modifier = modifier.padding(8.dp)) {
122 | Text(text = "Hi there!")
123 | Text(text = "Thanks for going through the Layouts codelab")
124 | ScrollingList()
125 | }
126 | }
127 |
128 |
129 | @Composable
130 | fun PhotographerCard() {
131 | Row(
132 | modifier = Modifier
133 | .padding(8.dp)
134 | .clip(RoundedCornerShape(4.dp))
135 | .background(MaterialTheme.colors.surface)
136 | .clickable(onClick = { /* Ignoring onClick */ })
137 | .padding(16.dp)
138 | ) {
139 | Surface(
140 | modifier = Modifier.size(50.dp),
141 | shape = CircleShape,
142 | color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)
143 | ) {
144 | // Image goes here
145 | }
146 |
147 | Column(
148 | modifier = Modifier
149 | .padding(start = 8.dp)
150 | .align(Alignment.CenterVertically)
151 | ) {
152 | Text("Alfred Sisley", fontWeight = FontWeight.Bold)
153 | // LocalContentAlpha is defining opacity level of its children
154 | CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
155 | Text("3 minutes ago", style = MaterialTheme.typography.body2)
156 | }
157 | }
158 | }
159 | }
160 |
161 | @Preview(showBackground = true)
162 | @Composable
163 | fun PhotographerCardPreview() {
164 | MobvenExtensionsTheme {
165 | LayoutsCodelab()
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobven/extensions/ViewExtDemoActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extensions
2 |
3 | import android.content.Intent
4 | import android.content.res.Configuration
5 | import android.graphics.Color
6 | import android.graphics.drawable.ColorDrawable
7 | import android.os.Bundle
8 | import android.os.IBinder
9 | import android.os.IInterface
10 | import android.util.Log
11 | import android.view.InputEvent
12 | import androidx.appcompat.app.AppCompatActivity
13 | import com.mobven.extension.click
14 | import com.mobven.extension.dialogs.*
15 | import com.mobven.extension.orFalse
16 | import com.mobven.extensions.databinding.ActivityViewExtDemoBinding
17 | import com.mobven.extensions.databinding.DialogBottomSheetBinding
18 | import com.mobven.extensions.databinding.DialogFullScreenBinding
19 | import com.mobven.extensions.onactivityresultapi.ChooseFromGalleryActivity
20 | import java.lang.reflect.InvocationTargetException
21 | import java.lang.reflect.Method
22 |
23 | class ViewExtDemoActivity : AppCompatActivity() {
24 |
25 | private lateinit var binding: ActivityViewExtDemoBinding
26 |
27 | companion object {
28 | private var inputManager: InputManager? = null
29 | private var GET_SERVICE_METHOD: Method? = Class.forName("android.os.ServiceManager")
30 | .getDeclaredMethod("getService", String::class.java)
31 | private var exposer: BottomSheetExposer? = null
32 | }
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | binding = ActivityViewExtDemoBinding.inflate(layoutInflater)
37 | setContentView(binding.root)
38 | testViewExtension()
39 | }
40 |
41 | private fun testViewExtension() {
42 | binding.apply {
43 | btnClickExt.click {
44 | fullScreenDialogOf(
45 | supportFragmentManager,
46 | R.style.FullScreenDialog,
47 | viewHolderCreator = { layoutInflater, dialog ->
48 | dialog.dialog?.getWindow()?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT));
49 | val binding = DialogFullScreenBinding.inflate(layoutInflater)
50 | binding.root
51 | })
52 | }
53 | btnChooseFromGallery.click {
54 | startActivity(
55 | Intent(
56 | this@ViewExtDemoActivity,
57 | ChooseFromGalleryActivity::class.java
58 | )
59 | )
60 | }
61 |
62 | btnBottomSheet.click {
63 | bottomSheetOf(
64 | supportFragmentManager,
65 | 40,
66 | dialogTheme = R.style.FullScreenDialog,
67 | viewHolderCreator = { layoutInflater, sheet ->
68 | exposer = sheet
69 | val binding = DialogBottomSheetBinding.inflate(layoutInflater)
70 | binding.root
71 | }
72 | )
73 | }
74 |
75 | btnAlertDialogExt.click {
76 | alert {
77 | setTitle("Title")
78 | setMessage("Message")
79 | positiveButton("Positive Button") {
80 |
81 | }
82 | negativeButton("Negative Button") {
83 |
84 | }
85 |
86 | }
87 | }
88 |
89 | btnCustomAlertDialogExt.click {
90 | /*customDialogOf(this@ViewExtDemoActivity, viewHolderCreator = { dialog ->
91 | val binding = CustomDialogBinding.inflate(layoutInflater).apply {
92 | btnPrimary.click {
93 | toast("Primary Button Clicked.")
94 | }
95 | }
96 | binding.root
97 | })*/
98 | /*val pM = PowerManager(
99 | getService(
100 | "power",
101 | "android.os.IPowerManager"
102 | )
103 | )
104 | Log.i("SCRCPY", pM.isScreenOn.toString())*/
105 | //Log.i("SCRCPY", getInputManager())
106 | }
107 | }
108 | }
109 |
110 | private fun getService(service: String, type: String): IInterface? {
111 | return try {
112 | val binder = GET_SERVICE_METHOD?.invoke(
113 | null,
114 | service
115 | ) as IBinder
116 | val asInterfaceMethod = Class.forName("$type\$Stub").getMethod(
117 | "asInterface",
118 | IBinder::class.java
119 | )
120 | asInterfaceMethod.invoke(null, binder) as IInterface
121 | } catch (e: Exception) {
122 | throw AssertionError(e)
123 | }
124 | }
125 |
126 | override fun onConfigurationChanged(newConfig: Configuration) {
127 | super.onConfigurationChanged(newConfig)
128 | exposer?.dismiss()
129 | }
130 |
131 | fun getInputManager(): InputManager? {
132 | if (inputManager == null) {
133 | try {
134 | val getInstanceMethod =
135 | android.hardware.input.InputManager::class.java.getDeclaredMethod("getInstance")
136 | val im = getInstanceMethod.invoke(null) as android.hardware.input.InputManager
137 | inputManager = InputManager(im)
138 | } catch (e: NoSuchMethodException) {
139 | throw java.lang.AssertionError(e)
140 | } catch (e: IllegalAccessException) {
141 | throw java.lang.AssertionError(e)
142 | } catch (e: InvocationTargetException) {
143 | throw java.lang.AssertionError(e)
144 | }
145 | }
146 | return inputManager
147 | }
148 |
149 | fun injectEvent(inputEvent: InputEvent?, displayId: Int, injectMode: Int): Boolean {
150 | return getInputManager()?.injectInputEvent(inputEvent, injectMode).orFalse()
151 | }
152 |
153 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/View.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.Canvas
6 | import android.graphics.Color
7 | import android.graphics.pdf.PdfRenderer
8 | import android.view.View
9 | import android.view.inputmethod.InputMethodManager
10 | import androidx.core.view.ViewCompat
11 | import androidx.core.view.updatePadding
12 | import androidx.core.widget.NestedScrollView
13 | import com.google.android.material.tabs.TabLayout
14 | import androidx.recyclerview.widget.LinearLayoutManager
15 | import androidx.recyclerview.widget.RecyclerView
16 |
17 | /**
18 | * Set an onClick listener
19 | */
20 | inline fun T.click(crossinline block: (T) -> Unit) = setOnClickListener {
21 | block(it as T)
22 | }
23 |
24 | /**
25 | * Show the view (visibility = View.VISIBLE)
26 | */
27 | fun View.show() {
28 | visibility = View.VISIBLE
29 | }
30 |
31 | /**
32 | * Remove the view (visibility = View.GONE)
33 | */
34 | fun View.gone() {
35 | visibility = View.GONE
36 | }
37 |
38 | /**
39 | * Try to hide the keyboard and returns whether it worked
40 | * https://stackoverflow.com/questions/1109022/close-hide-the-android-soft-keyboard
41 | */
42 | fun View.hideKeyboard(): Boolean {
43 | try {
44 | val inputMethodManager =
45 | context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
46 | return inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
47 | } catch (ignored: RuntimeException) {
48 | }
49 | return false
50 | }
51 |
52 | /**
53 | * Extension method to show a keyboard for View.
54 | */
55 | fun View.showKeyboard() {
56 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
57 | this.requestFocus()
58 | imm.showSoftInput(this, 0)
59 | }
60 |
61 | /**
62 | * Show the view if [condition] returns true
63 | * (visibility = View.VISIBLE)
64 | */
65 | inline fun View.showIf(condition: () -> Boolean): View {
66 | if (visibility != View.VISIBLE && condition()) {
67 | visibility = View.VISIBLE
68 | }
69 | return this
70 | }
71 |
72 | /**
73 | * Hide the view if [predicate] returns true
74 | * (visibility = View.INVISIBLE)
75 | */
76 | inline fun View.hideIf(predicate: () -> Boolean): View {
77 | if (visibility != View.INVISIBLE && predicate()) {
78 | visibility = View.INVISIBLE
79 | }
80 | return this
81 | }
82 |
83 | /**
84 | * Remove the view if [predicate] returns true
85 | * (visibility = View.GONE)
86 | */
87 | inline fun View.removeIf(predicate: () -> Boolean): View {
88 | if (visibility != View.GONE && predicate()) {
89 | visibility = View.GONE
90 | }
91 | return this
92 | }
93 |
94 | /**
95 | * Extension method for support multiple click
96 | */
97 | fun multipleOnClick(vararg view: View, onClick: () -> Unit) {
98 | view.forEach {
99 | it.setOnClickListener {
100 | onClick.invoke()
101 | }
102 | }
103 | }
104 |
105 | /**
106 | * Extension method for callback actions of tab selected, tab reselected and tab unselected events
107 | */
108 | fun TabLayout.onTabSelectedListener(
109 | onSelected: (TabLayout.Tab?) -> Unit,
110 | onReselected: (TabLayout.Tab?) -> Unit,
111 | onUnselected: (TabLayout.Tab?) -> Unit
112 | ) {
113 | addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
114 | override fun onTabSelected(tab: TabLayout.Tab?) {
115 | onSelected.invoke(tab)
116 | }
117 |
118 | override fun onTabReselected(tab: TabLayout.Tab?) {
119 | onReselected.invoke(tab)
120 | }
121 |
122 | override fun onTabUnselected(tab: TabLayout.Tab?) {
123 | onUnselected.invoke(tab)
124 | }
125 | })
126 | }
127 |
128 | /**
129 | * Extension method Provides editing for NestedScrollView in CoordinatorLayout.
130 | * INFO: Updates this NestedScrollView bottom-padding. It removes the empty-space.
131 | * Insets are areas of your view that you should not put elements, like behind the status bar or navigation bar.
132 | */
133 | fun NestedScrollView.setInsetListener() {
134 | ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
135 | this.updatePadding(bottom = insets.systemWindowInsetBottom)
136 | insets.consumeSystemWindowInsets()
137 | }
138 | }
139 |
140 | /**
141 | * Returns the scroll position for the vertical RecyclerView.
142 | * @param onScroll : Lambda ScrollPosition param
143 | */
144 |
145 | fun RecyclerView.addScrollListener(onScroll: (position: Int) -> Unit) {
146 | var lastPosition = 0
147 | addOnScrollListener(object : RecyclerView.OnScrollListener() {
148 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
149 | super.onScrolled(recyclerView, dx, dy)
150 | if (layoutManager is LinearLayoutManager) {
151 | val currentVisibleItemPosition =
152 | (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
153 |
154 | if (lastPosition != currentVisibleItemPosition && currentVisibleItemPosition != RecyclerView.NO_POSITION) {
155 | onScroll.invoke(currentVisibleItemPosition)
156 | lastPosition = currentVisibleItemPosition
157 | }
158 | }
159 | }
160 | })
161 | }
162 |
163 |
164 | /**
165 | @see @@@ Sample Usage @@@
166 | -------
167 |
168 | suspend fun renderSinglePage(filePath: String, width: Int) = withContext(Dispatchers.IO) {
169 | PdfRenderer(ParcelFileDescriptor.open(File(filePath), ParcelFileDescriptor.MODE_READ_ONLY)).use { renderer ->
170 | renderer.openPage(0).renderAndClose(width)
171 | }
172 | }
173 |
174 | */
175 |
176 | fun PdfRenderer.Page.renderAndClose(width: Int) = use {
177 | val bitmap = createBitmap(width)
178 | render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
179 | bitmap
180 | }
181 |
182 | private fun PdfRenderer.Page.createBitmap(bitmapWidth: Int): Bitmap {
183 | val bitmap = Bitmap.createBitmap(
184 | bitmapWidth, (bitmapWidth.toFloat() / width * height).toInt(), Bitmap.Config.ARGB_8888
185 | )
186 |
187 | val canvas = Canvas(bitmap)
188 | canvas.drawColor(Color.WHITE)
189 | canvas.drawBitmap(bitmap, 0f, 0f, null)
190 |
191 | return bitmap
192 | }
--------------------------------------------------------------------------------
/extension/src/main/java/com/mobven/extension/Context.kt:
--------------------------------------------------------------------------------
1 | package com.mobven.extension
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.app.PendingIntent
6 | import android.content.ActivityNotFoundException
7 | import android.content.Context
8 | import android.content.Intent
9 | import android.content.Intent.*
10 | import android.content.pm.PackageManager
11 | import android.graphics.Rect
12 | import android.graphics.drawable.Drawable
13 | import android.net.Uri
14 | import android.os.Build
15 | import android.os.Bundle
16 | import android.util.DisplayMetrics
17 | import android.util.TypedValue
18 | import android.view.WindowMetrics
19 | import android.widget.Toast
20 | import androidx.activity.ComponentActivity
21 | import androidx.activity.result.ActivityResultCallback
22 | import androidx.activity.result.ActivityResultLauncher
23 | import androidx.activity.result.contract.ActivityResultContracts
24 | import androidx.browser.customtabs.CustomTabColorSchemeParams
25 | import androidx.browser.customtabs.CustomTabsIntent
26 | import androidx.core.app.ActivityCompat
27 | import androidx.core.content.ContextCompat
28 |
29 |
30 | /**
31 | * Extension method to show toast for Context.
32 | */
33 | fun Context?.toast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) =
34 | this?.let { Toast.makeText(it, text, duration).show() }
35 |
36 | /**
37 | * Extension method to get height of screen
38 | */
39 | fun Activity.heightPixels(): Int {
40 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
41 | val windowMetrics: WindowMetrics = windowManager.maximumWindowMetrics
42 | val bounds: Rect = windowMetrics.bounds
43 | bounds.height()
44 | } else {
45 | val outMetrics = DisplayMetrics()
46 | @Suppress("DEPRECATION")
47 | val display = windowManager.defaultDisplay
48 | @Suppress("DEPRECATION")
49 | display.getMetrics(outMetrics)
50 | outMetrics.heightPixels
51 | }
52 | }
53 |
54 | /**
55 | * Extension method to get height of the full screen
56 | */
57 | fun Activity.heightFullScreenPixels(): Int {
58 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
59 | val windowMetrics: WindowMetrics = windowManager.maximumWindowMetrics
60 | val bounds: Rect = windowMetrics.bounds
61 | bounds.height()
62 | } else {
63 | val outMetrics = DisplayMetrics()
64 | @Suppress("DEPRECATION")
65 | val display = windowManager.defaultDisplay
66 | @Suppress("DEPRECATION")
67 | display.getRealMetrics(outMetrics)
68 | outMetrics.heightPixels
69 | }
70 | }
71 |
72 | /**
73 | * Extension method to get width of screen
74 | */
75 | fun Activity.widthPixels(): Int {
76 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
77 | val windowMetrics: WindowMetrics = windowManager.maximumWindowMetrics
78 | val bounds: Rect = windowMetrics.bounds
79 | bounds.width()
80 | } else {
81 | val outMetrics = DisplayMetrics()
82 | @Suppress("DEPRECATION")
83 | val display = windowManager.defaultDisplay
84 | @Suppress("DEPRECATION")
85 | display.getMetrics(outMetrics)
86 | outMetrics.widthPixels
87 | }
88 | }
89 |
90 | /**
91 | * Extension method to get width of screen
92 | */
93 | fun Activity.widthFullScreenPixels(): Int {
94 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
95 | val windowMetrics: WindowMetrics = windowManager.maximumWindowMetrics
96 | val bounds: Rect = windowMetrics.bounds
97 | bounds.width()
98 | } else {
99 | val outMetrics = DisplayMetrics()
100 | @Suppress("DEPRECATION")
101 | val display = windowManager.defaultDisplay
102 | @Suppress("DEPRECATION")
103 | display.getRealMetrics(outMetrics)
104 | outMetrics.widthPixels
105 | }
106 | }
107 |
108 | /**
109 | * Extension method to get drawable from resource
110 | */
111 | fun Context.drawable(res: Int): Drawable? = ContextCompat.getDrawable(this, res)
112 |
113 | /**
114 | * Extension method to provide simpler access to {@link ContextCompat#getColor(int)}.
115 | */
116 | fun Context.color(color: Int) = ContextCompat.getColor(this, color)
117 |
118 | /**
119 | * Extension method to convert dp to px
120 | */
121 | fun Context.dpToPixels(dp: Float): Int {
122 | val metrics = resources.displayMetrics
123 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics).toInt()
124 | }
125 |
126 | /**
127 | * Extension method to browse for Context.
128 | */
129 | fun Context.browse(url: String): Boolean {
130 | return try {
131 | val intent = Intent(ACTION_VIEW)
132 | intent.data = Uri.parse(url)
133 | startActivity(intent)
134 | true
135 | } catch (e: Exception) {
136 | false
137 | }
138 | }
139 |
140 | /**
141 | * Extension method to rate app on PlayStore for Context.
142 | */
143 | fun Context.market(): Boolean {
144 | return if (browse("market://details?id=$packageName")) {
145 | true
146 | } else {
147 | browse("http://play.google.com/store/apps/details?id=$packageName")
148 | }
149 | }
150 |
151 | /**
152 | * Extension method to share for Context.
153 | */
154 | fun Context.share(text: String, title: String): Boolean {
155 | val intent = Intent().apply {
156 | type = "text/plain"
157 | action = ACTION_SEND
158 | putExtra(EXTRA_TEXT, text)
159 | }
160 | return try {
161 | startActivity(createChooser(intent, title))
162 | true
163 | } catch (e: ActivityNotFoundException) {
164 | false
165 | }
166 | }
167 |
168 | /**
169 | * Extension method to send email for Context.
170 | */
171 | fun Context.email(email: String, subject: String = "", text: String = ""): Boolean {
172 | val intent = Intent(ACTION_SENDTO).apply {
173 | data = Uri.parse("mailto:")
174 | putExtra(EXTRA_EMAIL, arrayOf(email))
175 | if (subject.isNotBlank()) putExtra(EXTRA_SUBJECT, subject)
176 | if (text.isNotBlank()) putExtra(EXTRA_TEXT, text)
177 | }
178 | return try {
179 | startActivity(intent)
180 | true
181 | } catch (e: ActivityNotFoundException) {
182 | false
183 | }
184 | }
185 |
186 | /**
187 | * Extension method to make call for Context.
188 | */
189 | fun Context.makeCall(number: String): Boolean {
190 | return try {
191 | val intent = Intent(ACTION_CALL, Uri.parse("tel:$number"))
192 | startActivity(intent)
193 | true
194 | } catch (e: Exception) {
195 | false
196 | }
197 | }
198 |
199 | /**
200 | * Extension method to Send SMS for Context.
201 | */
202 | fun Context.sendSms(number: String, text: String = ""): Boolean {
203 | return try {
204 | val intent = Intent(ACTION_VIEW, Uri.parse("sms:$number")).apply {
205 | putExtra("sms_body", text)
206 | }
207 | startActivity(intent)
208 | true
209 | } catch (e: Exception) {
210 | false
211 | }
212 | }
213 |
214 | /**
215 | * Extension method to dail telephone number for Context.
216 | */
217 | fun Context.dial(tel: String?) = startActivity(Intent(ACTION_DIAL, Uri.parse("tel:$tel")))
218 |
219 | /**
220 | * Extension method to send sms for Context.
221 | */
222 | fun Context.sms(phone: String?, body: String = "") {
223 | val smsToUri = Uri.parse("smsto:$phone")
224 | val intent = Intent(ACTION_SENDTO, smsToUri)
225 | intent.putExtra("sms_body", body)
226 | startActivity(intent)
227 | }
228 |
229 | /**
230 | * Extension method to start activity with Intent extras
231 | */
232 | fun Context.startActivityWithExtras(it: Class, extras: Bundle.() -> Unit = {}) {
233 | val intent = Intent(this, it)
234 | intent.putExtras(Bundle().apply(extras))
235 | startActivity(intent)
236 | }
237 |
238 | /**
239 | * Shows url on Chrome Custam Tabs
240 | */
241 | fun Context.showUrlOnCustomTabs(
242 | url: String,
243 | shareState: Int = CustomTabsIntent.SHARE_STATE_OFF,
244 | navigationColor: Int = android.R.color.holo_green_dark,
245 | toolbarColor: Int = android.R.color.holo_blue_bright
246 | ) {
247 | try {
248 | CustomTabsIntent.Builder().apply {
249 | val params = CustomTabColorSchemeParams.Builder().apply {
250 | setNavigationBarColor(color(navigationColor))
251 | setToolbarColor(color(toolbarColor))
252 | }
253 | setShareState(shareState)
254 | .setDefaultColorSchemeParams(params.build())
255 | .build()
256 | .launchUrl(this@showUrlOnCustomTabs, Uri.parse(url))
257 | }
258 | } catch (e: Exception) {
259 | browse(url)
260 | }
261 | }
262 |
263 | /**
264 | * Launches a chooser for image or videos from gallery and returns list of choosed item URI's
265 | */
266 | fun ComponentActivity.chooseFromGallery(callback: ActivityResultCallback>): ActivityResultLauncher {
267 | return registerForActivityResult(ActivityResultContracts.GetMultipleContents(), callback)
268 | }
269 |
270 | /**
271 | * Launches system dialogs to ask user permissions and returns a result map
272 | */
273 | fun ComponentActivity.requestPermissions(callback: ActivityResultCallback