├── Helpers
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ ├── values
│ │ │ ├── styles.xml
│ │ │ └── attrs.xml
│ │ ├── anim
│ │ │ ├── scale_up_out.xml
│ │ │ └── scale_down_in.xml
│ │ └── anim-v11
│ │ │ ├── scale_up_out.xml
│ │ │ └── scale_down_in.xml
│ │ └── java
│ │ └── com
│ │ └── dg
│ │ ├── helpers
│ │ ├── BooleanHelper.kt
│ │ ├── ViewHelper.kt
│ │ ├── BigDecimalHelper.kt
│ │ ├── IntHelper.kt
│ │ ├── LongHelper.kt
│ │ ├── FloatHelper.kt
│ │ ├── SchedulerHandler.kt
│ │ ├── DoubleHelper.kt
│ │ ├── LocationHelper.kt
│ │ ├── ObjectHelper.kt
│ │ ├── StringHelper.kt
│ │ ├── DismissSoftkeyboardHelper.kt
│ │ ├── ExecutorHelper.kt
│ │ ├── DateHelper.kt
│ │ ├── ParcelHelper.kt
│ │ ├── FontHelper.kt
│ │ ├── TeaEncryptor.kt
│ │ ├── ConnectivityHelper.kt
│ │ ├── DbHelper.kt
│ │ ├── JSONHelper.kt
│ │ ├── FileHelper.kt
│ │ └── BitmapHelper.kt
│ │ ├── animations
│ │ ├── ResizeWidthAnimation.kt
│ │ ├── ResizeHeightAnimation.kt
│ │ ├── PopupWindowResizeWidthAnimation.kt
│ │ ├── PopupWindowResizeHeightAnimation.kt
│ │ ├── PopupWindowResizeAnimation.kt
│ │ └── ResizeAnimation.kt
│ │ ├── spannable
│ │ ├── CustomTypefaceSpannable.kt
│ │ └── SpannableStringBuilderEx.kt
│ │ └── controls
│ │ ├── ViewPagerEx.kt
│ │ ├── SwipeRefreshLayoutEx.kt
│ │ ├── BouncyListView.kt
│ │ ├── TextInputLayoutEx.kt
│ │ ├── CheckBoxEx.kt
│ │ ├── RadioButtonEx.kt
│ │ ├── ButtonEx.kt
│ │ ├── TextViewEx.kt
│ │ ├── TextInputEditTextEx.kt
│ │ ├── EditTextEx.kt
│ │ └── InfiniteScrollingListView.kt
├── proguard-rules.pro
├── gradle.properties
└── build.gradle
├── settings.gradle
├── gradle.properties
├── maven.gpg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── LICENSE
├── gradlew.bat
├── sonatype-push.gradle
├── README.md
└── gradlew
/Helpers/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':Helpers'
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.useAndroidX=true
2 | android.enableJetifier=true
3 |
--------------------------------------------------------------------------------
/maven.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielgindi/android-helpers/HEAD/maven.gpg
--------------------------------------------------------------------------------
/Helpers/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielgindi/android-helpers/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Helpers/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Oct 06 21:35:50 IDT 2018
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-7.4-bin.zip
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # gimpy mac thingy
2 | .DS_Store
3 |
4 | # IDEA Ignores
5 | *.iml
6 | *.ipr
7 | *.iws
8 | .idea/
9 |
10 | # Generated files
11 | bin/
12 | gen/
13 |
14 | # Built application files
15 | *.apk
16 | *.ap_
17 |
18 | # Files for the Dalvik VM
19 | *.dex
20 |
21 | # Java class files
22 | *.class
23 |
24 | # Gradle files
25 | .gradle/
26 | build/
27 |
28 | # Local configuration file (sdk path, etc)
29 | local.properties
30 |
31 | # Proguard folder generated by Eclipse
32 | proguard/
33 |
34 | # Log Files
35 | *.log
36 |
37 |
--------------------------------------------------------------------------------
/Helpers/src/main/res/anim/scale_up_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/Helpers/src/main/res/anim/scale_down_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Helpers/src/main/res/anim-v11/scale_up_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/Helpers/src/main/res/anim-v11/scale_down_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/BooleanHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | @Suppress("unused")
4 | object BooleanHelper
5 | {
6 | @Suppress("MemberVisibilityCanBePrivate")
7 | fun withObject(value: Any?): Boolean?
8 | {
9 | return if (value == null)
10 | null
11 | else value as? Boolean ?: when (value)
12 | {
13 | is Int -> value != 0
14 | is Short -> value != 0
15 | else -> null
16 | }
17 | }
18 |
19 | fun withObject(value: Any?, defaultValue: Boolean): Boolean
20 | {
21 | return withObject(value) ?: defaultValue
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Helpers/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/scottab/dev/adt-bundle-mac-x86_64/sdk-macosx-v2/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/animations/ResizeWidthAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.dg.animations
2 |
3 | import android.view.View
4 |
5 | @Suppress("unused")
6 | class ResizeWidthAnimation : ResizeAnimation
7 | {
8 | /**
9 | * Constructs a resize animation
10 | * @param view - the view to animate
11 | * @param fromWidth - the original width to animate from.
12 | * Use `CURRENT_SIZE` to set from current width automatically.
13 | * @param toWidth - the target width to animate to.
14 | * Use `CURRENT_SIZE` to set from current width automatically.
15 | */
16 | @Suppress("ConvertSecondaryConstructorToPrimary")
17 | constructor(view: View,
18 | fromWidth: Int,
19 | toWidth: Int) : super(view, fromWidth, toWidth, CURRENT_SIZE, CURRENT_SIZE)
20 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/animations/ResizeHeightAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.dg.animations
2 |
3 | import android.view.View
4 |
5 | @Suppress("unused")
6 | class ResizeHeightAnimation : ResizeAnimation
7 | {
8 | /**
9 | * Constructs a resize animation
10 | * @param view - the view to animate
11 | * @param fromHeight - the original height to animate from.
12 | * Use `CURRENT_SIZE` to set from current height automatically.
13 | * @param toHeight - the target height to animate to.
14 | * Use `CURRENT_SIZE` to set from current height automatically.
15 | */
16 | @Suppress("ConvertSecondaryConstructorToPrimary")
17 | constructor(view: View,
18 | fromHeight: Int,
19 | toHeight: Int) : super(view, CURRENT_SIZE, CURRENT_SIZE, fromHeight, toHeight)
20 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/ViewHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.view.View
4 |
5 | @Suppress("unused")
6 | object ViewHelper
7 | {
8 | /**
9 | * Force a measure and re-layout of children at this instant.
10 | * This is useful when "requestLayout" is too late and causes visual "jumps".
11 | *
12 | * Note: If a view's width/height are 0, then you probably need to re-layout its *parent*.
13 | * @param view
14 | */
15 | fun layoutNow(view: View)
16 | {
17 | view.measure(
18 | View.MeasureSpec.makeMeasureSpec(view.measuredWidth, View.MeasureSpec.EXACTLY),
19 | View.MeasureSpec.makeMeasureSpec(view.measuredHeight, View.MeasureSpec.EXACTLY))
20 | view.layout(view.left,
21 | view.top,
22 | view.right,
23 | view.bottom)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/animations/PopupWindowResizeWidthAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.dg.animations
2 |
3 | import android.widget.PopupWindow
4 |
5 | @Suppress("unused")
6 | class PopupWindowResizeWidthAnimation : PopupWindowResizeAnimation
7 | {
8 | /**
9 | * Constructs a resize animation
10 | * @param popupWindow - the popup to animate
11 | * @param fromWidth - the original width to animate from.
12 | * Use `CURRENT_SIZE` to set from current width automatically.
13 | * @param toWidth - the target width to animate to.
14 | * Use `CURRENT_SIZE` to set from current width automatically.
15 | */
16 | @Suppress("ConvertSecondaryConstructorToPrimary")
17 | constructor(popupWindow: PopupWindow,
18 | fromWidth: Int,
19 | toWidth: Int)
20 | : super(popupWindow,
21 | CURRENT_SIZE, CURRENT_SIZE,
22 | fromWidth, toWidth)
23 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/animations/PopupWindowResizeHeightAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.dg.animations
2 |
3 | import android.widget.PopupWindow
4 |
5 | @Suppress("unused")
6 | class PopupWindowResizeHeightAnimation : PopupWindowResizeAnimation
7 | {
8 | /**
9 | * Constructs a resize animation
10 | * @param popupWindow - the popup to animate
11 | * @param fromHeight - the original height to animate from.
12 | * Use `CURRENT_SIZE` to set from current height automatically.
13 | * @param toHeight - the target height to animate to.
14 | * Use `CURRENT_SIZE` to set from current height automatically.
15 | */
16 | @Suppress("ConvertSecondaryConstructorToPrimary")
17 | constructor(popupWindow: PopupWindow,
18 | fromHeight: Int,
19 | toHeight: Int)
20 | : super(popupWindow,
21 | CURRENT_SIZE, CURRENT_SIZE,
22 | fromHeight, toHeight)
23 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/BigDecimalHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import java.math.BigDecimal
4 |
5 | @Suppress("unused")
6 | object BigDecimalHelper
7 | {
8 | @Suppress("MemberVisibilityCanBePrivate")
9 | fun withObject(value: Any?): BigDecimal?
10 | {
11 | return if (value == null)
12 | null
13 | else value as? BigDecimal ?: when (value)
14 | {
15 | is Double -> if (value.isNaN()) null else BigDecimal(value)
16 | is Float -> if (value.isNaN()) null else BigDecimal(value.toDouble())
17 | is Int -> BigDecimal(value)
18 | is Short -> BigDecimal(value.toInt())
19 | is String -> BigDecimal(value)
20 | else -> null
21 | }
22 | }
23 |
24 | fun withObject(value: Any?, defaultValue: BigDecimal): BigDecimal
25 | {
26 | return withObject(value) ?: defaultValue
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Helpers/gradle.properties:
--------------------------------------------------------------------------------
1 | signing.keyId = 6891374A
2 | signing.password =
3 | signing.secretKeyRingFile = ../maven.gpg
4 |
5 | GROUP = com.github.danielgindi
6 | VERSION_NAME = 1.4.16
7 | POM_NAME = Helpers
8 | POM_ARTIFACT_ID = helpers
9 | POM_PACKAGING = aar
10 | POM_DESCRIPTION = A set of helper classes for Android.
11 | POM_URL = https://github.com/danielgindi/android-helpers.git
12 | POM_SCM_URL = https://github.com/danielgindi/android-helpers.git
13 | POM_SCM_CONNECTION = scm:git@github.com:danielgindi/android-helpers.git
14 | POM_SCM_DEV_CONNECTION = scm:git@github.com:danielgindi/android-helpers.git
15 | POM_LICENSE_NAME = MIT
16 | POM_LICENSE_URL = https://raw.githubusercontent.com/danielgindi/KotlinEval/master/LICENSE
17 | POM_LICENSE_DIST = repo
18 | POM_DEVELOPER_ID = danielgindi
19 | POM_DEVELOPER_NAME = Daniel Cohen Gindi
20 | POM_DEVELOPER_EMAIL = danielgindi@gmail.com
21 |
--------------------------------------------------------------------------------
/Helpers/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Daniel Cohen Gindi
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 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/IntHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | @Suppress("unused")
4 | object IntHelper
5 | {
6 | @Suppress("MemberVisibilityCanBePrivate")
7 | fun withObject(value: Any?): Int?
8 | {
9 | when (value)
10 | {
11 | null -> return null
12 | is Int -> return value
13 | is Short -> return value.toInt()
14 | is String -> try
15 | {
16 | return Integer.parseInt(value)
17 | }
18 | catch (ignored: Exception)
19 | {
20 | }
21 | }
22 | return null
23 | }
24 |
25 | fun withObject(value: Any?, defaultValue: Int): Int
26 | {
27 | return withObject(value) ?: defaultValue
28 | }
29 |
30 | fun toArray(inArray: Array): IntArray
31 | {
32 | val array = IntArray(inArray.size)
33 | var i = 0
34 | val len = inArray.size
35 | while (i < len)
36 | {
37 | val value = inArray[i]
38 |
39 | if (value != null)
40 | {
41 | array[i] = value
42 | }
43 | i++
44 | }
45 | return array
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Helpers/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | namespace = 'com.dg'
6 | compileSdkVersion 33
7 | buildToolsVersion "33.0.2"
8 |
9 | defaultConfig {
10 | minSdkVersion 14
11 | targetSdkVersion 33
12 | versionCode versionCode
13 | versionName version
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | lintOptions {
22 | abortOnError false
23 | }
24 | publishing {
25 | singleVariant('release') {
26 | withSourcesJar()
27 | withJavadocJar()
28 | }
29 | }
30 | }
31 |
32 | kotlin {
33 | jvmToolchain(8)
34 | }
35 |
36 | dependencies {
37 | compileOnly 'androidx.appcompat:appcompat:1.6.1'
38 | compileOnly 'androidx.exifinterface:exifinterface:1.3.6'
39 | compileOnly 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
40 | compileOnly 'com.google.android.material:material:1.9.0-beta01'
41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
42 | }
43 |
44 | apply from: '../sonatype-push.gradle'
45 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/LongHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | @Suppress("unused")
4 | object LongHelper
5 | {
6 | @Suppress("MemberVisibilityCanBePrivate")
7 | fun withObject(value: Any?): Long?
8 | {
9 | when (value)
10 | {
11 | null -> return null
12 | is Long -> return value
13 | is Int -> return value.toLong()
14 | is Short -> return value.toLong()
15 | is String -> try
16 | {
17 | return java.lang.Long.parseLong(value)
18 | }
19 | catch (ignored: Exception)
20 | {
21 | }
22 | }
23 | return null
24 | }
25 |
26 | fun withObject(value: Any?, defaultValue: Long): Long
27 | {
28 | return withObject(value) ?: defaultValue
29 | }
30 |
31 | fun toArray(inArray: Array): LongArray
32 | {
33 | val array = LongArray(inArray.size)
34 | var i = 0
35 | val len = inArray.size
36 | while (i < len)
37 | {
38 | val value = inArray[i]
39 |
40 | if (value != null)
41 | {
42 | array[i] = value
43 | }
44 | i++
45 | }
46 | return array
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/spannable/CustomTypefaceSpannable.kt:
--------------------------------------------------------------------------------
1 | package com.dg.spannable
2 |
3 | import android.graphics.Paint
4 | import android.graphics.Typeface
5 | import android.text.TextPaint
6 | import android.text.style.MetricAffectingSpan
7 |
8 | /**
9 | * Changes the typeface family of the text to which the span is attached.
10 | */
11 | @Suppress("unused")
12 | class CustomTypefaceSpannable : MetricAffectingSpan
13 | {
14 | @Suppress("MemberVisibilityCanBePrivate")
15 | val typeface: Typeface
16 |
17 | @Suppress("ConvertSecondaryConstructorToPrimary")
18 | constructor(typeface: Typeface) : super()
19 | {
20 | this.typeface = typeface
21 | }
22 |
23 | val spanTypeId: Int
24 | get() = SPANNABLE_ID
25 |
26 | fun describeContents(): Int
27 | {
28 | return 0
29 | }
30 |
31 | override fun updateDrawState(ds: TextPaint)
32 | {
33 | apply(ds, typeface)
34 | }
35 |
36 | override fun updateMeasureState(paint: TextPaint)
37 | {
38 | apply(paint, typeface)
39 | }
40 |
41 | companion object
42 | {
43 | private const val SPANNABLE_ID = 100
44 |
45 | private fun apply(paint: Paint, typeface: Typeface)
46 | {
47 | paint.typeface = typeface
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/FloatHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | @Suppress("unused")
4 | object FloatHelper
5 | {
6 | @Suppress("MemberVisibilityCanBePrivate")
7 | fun withObject(value: Any?): Float?
8 | {
9 | when (value)
10 | {
11 | null -> return null
12 | is Double -> return value.toFloat()
13 | is Float -> return value
14 | is Int -> return value.toFloat()
15 | is Short -> return value.toFloat()
16 | is String -> try
17 | {
18 | return java.lang.Float.parseFloat(value)
19 | }
20 | catch (ignored: Exception)
21 | {
22 | }
23 | }
24 | return null
25 | }
26 |
27 | fun withObject(value: Any?, defaultValue: Float): Float
28 | {
29 | return withObject(value) ?: defaultValue
30 | }
31 |
32 | fun toArray(inArray: Array): FloatArray
33 | {
34 | val array = FloatArray(inArray.size)
35 | var i = 0
36 | val len = inArray.size
37 | while (i < len)
38 | {
39 | val value = inArray[i]
40 |
41 | if (value != null)
42 | {
43 | array[i] = value
44 | }
45 | i++
46 | }
47 | return array
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/SchedulerHandler.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 |
6 | import java.util.HashMap
7 |
8 | @Suppress("unused")
9 | class SchedulerHandler : Handler
10 | {
11 | private val mTasks = HashMap()
12 |
13 | constructor() : super()
14 |
15 | constructor(callback: Callback) : super(callback)
16 |
17 | constructor(looper: Looper) : super(looper)
18 |
19 | constructor(looper: Looper, callback: Callback) : super(looper, callback)
20 |
21 | @Suppress("MemberVisibilityCanBePrivate")
22 | fun postWithSchedule(task: Runnable, delay: Long, period: Long)
23 | {
24 | val runnable = object : Runnable
25 | {
26 | override fun run()
27 | {
28 | task.run()
29 | postDelayed(this, period)
30 | }
31 | }
32 |
33 | mTasks[task] = runnable
34 |
35 | postDelayed(runnable, delay)
36 | }
37 |
38 | fun postWithSchedule(task: Runnable, period: Long)
39 | {
40 | postWithSchedule(task, period, period)
41 | }
42 |
43 | fun removeScheduledTask(task: Runnable)
44 | {
45 | val removed = mTasks.remove(task) ?: return
46 |
47 | removeCallbacks(removed)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/DoubleHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | @Suppress("unused")
4 | object DoubleHelper
5 | {
6 | @Suppress("MemberVisibilityCanBePrivate")
7 | fun withObject(value: Any?): Double?
8 | {
9 | when (value)
10 | {
11 | null -> return null
12 | is Double -> return value
13 | is Float -> return value.toDouble()
14 | is Int -> return value.toDouble()
15 | is Short -> return value.toDouble()
16 | is String -> try
17 | {
18 | return java.lang.Double.parseDouble(value)
19 | }
20 | catch (ignored: Exception)
21 | {
22 | }
23 | }
24 | return null
25 | }
26 |
27 | @Suppress("MemberVisibilityCanBePrivate")
28 | fun withObject(value: Any?, defaultValue: Double): Double
29 | {
30 | return withObject(value) ?: defaultValue
31 | }
32 |
33 | fun toArray(inArray: Array): FloatArray
34 | {
35 | val array = FloatArray(inArray.size)
36 | var i = 0
37 | val len = inArray.size
38 | while (i < len)
39 | {
40 | val value = inArray[i]
41 |
42 | if (value != null)
43 | {
44 | array[i] = value
45 | }
46 | i++
47 | }
48 | return array
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/LocationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import kotlin.math.atan2
4 | import kotlin.math.cos
5 | import kotlin.math.sin
6 | import kotlin.math.sqrt
7 |
8 | @Suppress("unused")
9 | object LocationHelper
10 | {
11 | private const val R = 6371.0
12 | private const val M_PI = Math.PI
13 | private const val DEG_TO_RAD = M_PI / 180.0
14 |
15 | /**
16 | * Calculates the distance between the specified locations
17 | * @param latitude1
18 | * @param longitude1
19 | * @param latitude2
20 | * @param longitude2
21 | * @return Distance in kilometers
22 | */
23 | fun getDistanceBetweenCoordinates(
24 | latitude1: Double, longitude1: Double,
25 | latitude2: Double, longitude2: Double): Double
26 | {
27 | var lat1 = latitude1
28 | var lon1 = longitude1
29 | var lat2 = latitude2
30 | var lon2 = longitude2
31 |
32 | lat1 *= DEG_TO_RAD
33 | lon1 *= DEG_TO_RAD
34 | lat2 *= DEG_TO_RAD
35 | lon2 *= DEG_TO_RAD
36 | val latDelta = lat2 - lat1
37 | val longitudeDelta = lon2 - lon1
38 |
39 | val a = sin(latDelta / 2.0) * sin(latDelta / 2.0) + cos(lat1) * cos(lat2) *
40 | sin(longitudeDelta / 2.0) * sin(longitudeDelta / 2.0)
41 | val c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a))
42 | return R * c // Kilometers
43 | }
44 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/ViewPagerEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.MotionEvent
6 | import androidx.viewpager.widget.ViewPager
7 | import com.dg.R
8 |
9 | @Suppress("unused")
10 | class ViewPagerEx : ViewPager
11 | {
12 | @Suppress("MemberVisibilityCanBePrivate")
13 | internal var mSwipeEnabled = true
14 |
15 | constructor(context: Context) : super(context)
16 |
17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
18 | {
19 |
20 | val styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerEx)
21 | try
22 | {
23 | mSwipeEnabled = styledAttributes.getBoolean(R.styleable.ViewPagerEx_swipeEnabled, true)
24 | }
25 | finally
26 | {
27 | styledAttributes.recycle()
28 | }
29 | }
30 |
31 | override fun onInterceptTouchEvent(event: MotionEvent): Boolean
32 | {
33 | return if (mSwipeEnabled)
34 | {
35 | super.onInterceptTouchEvent(event)
36 | }
37 | else false
38 |
39 | // Never allow swiping to switch between pages
40 | }
41 |
42 | override fun onTouchEvent(event: MotionEvent): Boolean
43 | {
44 | return if (mSwipeEnabled)
45 | {
46 | super.onTouchEvent(event)
47 | }
48 | else false
49 |
50 | // Never allow swiping to switch between pages
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/SwipeRefreshLayoutEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
5 | import android.util.AttributeSet
6 |
7 | @Suppress("unused")
8 | class SwipeRefreshLayoutEx : SwipeRefreshLayout
9 | {
10 | private var mMeasured = false
11 | private var mHasPostponedSetRefreshing = false
12 | private var mPreMeasureRefreshing = false
13 |
14 | constructor(context: Context) : super(context)
15 |
16 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
17 |
18 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
19 | {
20 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
21 |
22 | // There's a bug where setRefreshing(...) before onMeasure() has no effect at all.
23 | if (!mMeasured)
24 | {
25 | mMeasured = true
26 |
27 | if (mHasPostponedSetRefreshing)
28 | {
29 | isRefreshing = mPreMeasureRefreshing
30 | }
31 | }
32 | }
33 |
34 | override fun setRefreshing(refreshing: Boolean)
35 | {
36 | if (mMeasured)
37 | {
38 | mHasPostponedSetRefreshing = false
39 | super.setRefreshing(refreshing)
40 | }
41 | else
42 | {
43 | // onMeasure was not called yet, so we postpone the setRefreshing until after onMeasure
44 | mHasPostponedSetRefreshing = true
45 | mPreMeasureRefreshing = refreshing
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/ObjectHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import org.json.JSONObject
4 |
5 | @Suppress("unused")
6 | object ObjectHelper
7 | {
8 | private val JSON_NULL = JSONObject.NULL
9 |
10 | /**
11 | * Checks for equality between two objects.
12 | * * Normalizes `JSONObject.NULL` to null
13 | * * Successfully compares Float with Double
14 | * * Successfully compares Integer with Long
15 | * @param left
16 | * @param right
17 | * @return true if equal
18 | */
19 | fun equals(left: Any?, right: Any?): Boolean
20 | {
21 | @Suppress("NAME_SHADOWING")
22 | var left = left
23 | @Suppress("NAME_SHADOWING")
24 | var right = right
25 |
26 | if (left === JSON_NULL)
27 | {
28 | left = null
29 | }
30 |
31 | if (right === JSON_NULL)
32 | {
33 | right = null
34 | }
35 |
36 | if (left == null && right != null)
37 | {
38 | return false
39 | }
40 |
41 | if (right == null && left != null)
42 | {
43 | return false
44 | }
45 |
46 | if (left == null)
47 | {
48 | return true
49 | }
50 |
51 | if (left is Float && right is Double)
52 | {
53 | return right == left.toDouble()
54 | }
55 |
56 | if (right is Float && left is Double)
57 | {
58 | return left == right.toDouble()
59 | }
60 |
61 | if (left is Int && right is Long)
62 | {
63 | return right == left.toLong()
64 | }
65 |
66 | return if (right is Int && left is Long)
67 | {
68 | left == right.toLong()
69 | }
70 | else left == right
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/BouncyListView.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.widget.ListView
6 |
7 | @Suppress("unused")
8 | class BouncyListView : ListView
9 | {
10 | private var maxYOverscrollDistance: Int = 0
11 |
12 | constructor(context: Context) : super(context)
13 | {
14 | initBounceListView()
15 | }
16 |
17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
18 | {
19 | initBounceListView()
20 | }
21 |
22 | constructor(context: Context,
23 | attrs: AttributeSet,
24 | defStyle: Int) : super(context, attrs, defStyle)
25 | {
26 | initBounceListView()
27 | }
28 |
29 | private fun initBounceListView()
30 | {
31 | setMaxYOverscrollDistance(200)
32 | }
33 |
34 | fun setMaxYOverscrollDistance(maxYOverscrollDistance: Int)
35 | {
36 | val metrics = context!!.resources.displayMetrics
37 | val density = metrics.density
38 |
39 | this.maxYOverscrollDistance = (density * maxYOverscrollDistance).toInt()
40 | }
41 |
42 | override fun overScrollBy(deltaX: Int,
43 | deltaY: Int,
44 | scrollX: Int,
45 | scrollY: Int,
46 | scrollRangeX: Int,
47 | scrollRangeY: Int,
48 | maxOverScrollX: Int,
49 | maxOverScrollY: Int,
50 | isTouchEvent: Boolean): Boolean
51 | {
52 | return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, this.maxYOverscrollDistance, isTouchEvent)
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/TextInputLayoutEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Typeface
6 | import android.util.AttributeSet
7 | import com.google.android.material.textfield.TextInputLayout
8 | import com.dg.R
9 | import com.dg.helpers.FontHelper
10 |
11 | @Suppress("unused")
12 | class TextInputLayoutEx : TextInputLayout
13 | {
14 | constructor(context: Context) : super(context)
15 |
16 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
17 | {
18 | setCustomFontFamily(context, attrs)
19 | }
20 |
21 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context,
22 | attrs,
23 | defStyle)
24 | {
25 | setCustomFontFamily(context, attrs)
26 | }
27 |
28 | private fun setCustomFontFamily(context: Context, attrs: AttributeSet)
29 | {
30 | if (isInEditMode)
31 | {
32 | return
33 | }
34 |
35 | var fontFamily: String? = null
36 | var styledAttributes: TypedArray? = null
37 | try
38 | {
39 | styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.TextInputLayoutEx)
40 | fontFamily = styledAttributes!!.getString(R.styleable.TextInputLayoutEx_customFontFamily)
41 | }
42 | finally
43 | {
44 | styledAttributes?.recycle()
45 | }
46 |
47 | if (fontFamily != null && !fontFamily.isEmpty())
48 | {
49 | setCustomFont(fontFamily)
50 | }
51 | }
52 |
53 | fun setCustomFont(fontFamily: String): Boolean
54 | {
55 | val typeface: Typeface? = FontHelper.getFont(context, fontFamily)
56 | return if (typeface != null)
57 | {
58 | setTypeface(typeface)
59 | true
60 | }
61 | else
62 | {
63 | false
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/CheckBoxEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Paint
6 | import android.graphics.Typeface
7 | import android.util.AttributeSet
8 | import androidx.appcompat.widget.AppCompatCheckBox
9 | import com.dg.R
10 | import com.dg.helpers.FontHelper
11 |
12 | @Suppress("unused")
13 | class CheckBoxEx : AppCompatCheckBox
14 | {
15 | constructor(context: Context) : super(context)
16 |
17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
18 | {
19 | setCustomFontFamily(context, attrs)
20 | }
21 |
22 | constructor(context: Context,
23 | attrs: AttributeSet,
24 | defStyle: Int) : super(context, attrs, defStyle)
25 | {
26 | setCustomFontFamily(context, attrs)
27 | }
28 |
29 | private fun setCustomFontFamily(context: Context, attrs: AttributeSet)
30 | {
31 | if (isInEditMode)
32 | {
33 | return
34 | }
35 |
36 | val fontFamily: String?
37 | var styledAttributes: TypedArray? = null
38 | try
39 | {
40 | styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.CheckBoxEx)
41 | fontFamily = styledAttributes!!.getString(R.styleable.CheckBoxEx_customFontFamily)
42 | }
43 | finally
44 | {
45 | styledAttributes?.recycle()
46 | }
47 |
48 | if (fontFamily != null && !fontFamily.isEmpty())
49 | {
50 | paintFlags = this.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
51 | setCustomFont(fontFamily)
52 | }
53 | }
54 |
55 | fun setCustomFont(fontFamily: String): Boolean
56 | {
57 | val typeface: Typeface? = FontHelper.getFont(context, fontFamily)
58 | return if (typeface != null)
59 | {
60 | setTypeface(typeface)
61 | true
62 | }
63 | else
64 | {
65 | false
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/RadioButtonEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Paint
6 | import android.graphics.Typeface
7 | import android.util.AttributeSet
8 | import androidx.appcompat.widget.AppCompatCheckBox
9 | import androidx.appcompat.widget.AppCompatRadioButton
10 | import com.dg.R
11 | import com.dg.helpers.FontHelper
12 |
13 | @Suppress("unused")
14 | class RadioButtonEx : AppCompatRadioButton
15 | {
16 | constructor(context: Context) : super(context)
17 |
18 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
19 | {
20 | setCustomFontFamily(context, attrs)
21 | }
22 |
23 | constructor(context: Context,
24 | attrs: AttributeSet,
25 | defStyle: Int) : super(context, attrs, defStyle)
26 | {
27 | setCustomFontFamily(context, attrs)
28 | }
29 |
30 | private fun setCustomFontFamily(context: Context, attrs: AttributeSet)
31 | {
32 | if (isInEditMode)
33 | {
34 | return
35 | }
36 |
37 | val fontFamily: String?
38 | var styledAttributes: TypedArray? = null
39 | try
40 | {
41 | styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.RadioButtonEx)
42 | fontFamily = styledAttributes!!.getString(R.styleable.RadioButtonEx_customFontFamily)
43 | }
44 | finally
45 | {
46 | styledAttributes?.recycle()
47 | }
48 |
49 | if (fontFamily != null && !fontFamily.isEmpty())
50 | {
51 | paintFlags = this.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
52 | setCustomFont(fontFamily)
53 | }
54 | }
55 |
56 | fun setCustomFont(fontFamily: String): Boolean
57 | {
58 | val typeface: Typeface? = FontHelper.getFont(context, fontFamily)
59 | return if (typeface != null)
60 | {
61 | setTypeface(typeface)
62 | true
63 | }
64 | else
65 | {
66 | false
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/StringHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import java.text.DecimalFormat
4 | import java.text.DecimalFormatSymbols
5 | import java.util.Locale
6 | import java.util.regex.Pattern
7 |
8 | @Suppress("unused")
9 | object StringHelper
10 | {
11 | private var sCleanDecimalFormatter: DecimalFormat? = null
12 |
13 | private val cleanDecimalFormatter: DecimalFormat
14 | get()
15 | {
16 | if (sCleanDecimalFormatter == null)
17 | {
18 | val df = DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.getDefault()))
19 | df.maximumFractionDigits = 340
20 | sCleanDecimalFormatter = df
21 | }
22 | return sCleanDecimalFormatter as DecimalFormat
23 | }
24 |
25 | @Suppress("MemberVisibilityCanBePrivate")
26 | fun withObject(value: Any?): String?
27 | {
28 | when (value)
29 | {
30 | null -> return null
31 | is String -> return value
32 | is Double ->
33 | {
34 | return if (value.toLong().toDouble() == value)
35 | {
36 | value.toLong().toString()
37 | }
38 | else
39 | {
40 | cleanDecimalFormatter.format(value)
41 | }
42 | }
43 | is Float ->
44 | {
45 | return if (value.toLong().toFloat() == value)
46 | {
47 | value.toLong().toString()
48 | }
49 | else
50 | {
51 | cleanDecimalFormatter.format(value.toDouble())
52 | }
53 | }
54 | else -> return value.toString()
55 | }
56 | }
57 |
58 | fun withObject(value: Any?, defaultValue: String): String
59 | {
60 | return withObject(value) ?: defaultValue
61 | }
62 |
63 | private val mEmailPattern = Pattern.compile("^[a-zA-Z0-9\\._%+\\-]+@[a-zA-Z0-9\\-]+(\\.[a-zA-Z0-9\\-]+)*$")
64 |
65 | fun isValidEmailAddress(email: String): Boolean
66 | {
67 | return mEmailPattern.matcher(email).matches()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/ButtonEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Paint
6 | import android.graphics.Typeface
7 | import android.util.AttributeSet
8 | import androidx.appcompat.widget.AppCompatButton
9 | import com.dg.R
10 | import com.dg.helpers.FontHelper
11 |
12 | @Suppress("unused")
13 | class ButtonEx : AppCompatButton
14 | {
15 | constructor(context: Context) : super(context)
16 |
17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
18 | {
19 | setCustomFontFamily(context, attrs)
20 | }
21 |
22 | constructor(context: Context,
23 | attrs: AttributeSet,
24 | defStyle: Int) : super(context, attrs, defStyle)
25 | {
26 | setCustomFontFamily(context, attrs)
27 | }
28 |
29 | private fun setCustomFontFamily(context: Context, attrs: AttributeSet)
30 | {
31 | if (isInEditMode)
32 | {
33 | return
34 | }
35 |
36 | val fontFamily: String?
37 | var styledAttributes: TypedArray? = null
38 | try
39 | {
40 | styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.ButtonEx)
41 | fontFamily = styledAttributes!!.getString(R.styleable.ButtonEx_customFontFamily)
42 | }
43 | finally
44 | {
45 | styledAttributes?.recycle()
46 | }
47 |
48 | if (fontFamily != null && !fontFamily.isEmpty())
49 | {
50 | paintFlags = this.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
51 | setCustomFont(fontFamily)
52 | }
53 | }
54 |
55 | @Deprecated("This version was a mistake", replaceWith = ReplaceWith("setCustomFont(fontFamily)"))
56 | fun setCustomFont(context: Context, fontFamily: String): Boolean
57 | {
58 | return setCustomFont(fontFamily)
59 | }
60 |
61 | fun setCustomFont(fontFamily: String): Boolean
62 | {
63 | val typeface: Typeface? = FontHelper.getFont(context, fontFamily)
64 | return if (typeface != null)
65 | {
66 | setTypeface(typeface)
67 | true
68 | }
69 | else
70 | {
71 | false
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/TextViewEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Paint
6 | import android.graphics.Typeface
7 | import android.util.AttributeSet
8 | import androidx.appcompat.widget.AppCompatTextView
9 | import com.dg.R
10 | import com.dg.helpers.FontHelper
11 |
12 | @Suppress("unused")
13 | class TextViewEx : AppCompatTextView
14 | {
15 | constructor(context: Context) : super(context)
16 |
17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
18 | {
19 | setCustomFontFamily(context, attrs)
20 | }
21 |
22 | constructor(context: Context,
23 | attrs: AttributeSet,
24 | defStyle: Int) : super(context, attrs, defStyle)
25 | {
26 | setCustomFontFamily(context, attrs)
27 | }
28 |
29 | private fun setCustomFontFamily(context: Context, attrs: AttributeSet)
30 | {
31 | if (isInEditMode)
32 | {
33 | return
34 | }
35 |
36 | val fontFamily: String?
37 | var styledAttributes: TypedArray? = null
38 | try
39 | {
40 | styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.TextViewEx)
41 | fontFamily = styledAttributes!!.getString(R.styleable.TextViewEx_customFontFamily)
42 | }
43 | finally
44 | {
45 | styledAttributes?.recycle()
46 | }
47 |
48 | if (fontFamily != null && !fontFamily.isEmpty())
49 | {
50 | paintFlags = this.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
51 | setCustomFont(fontFamily)
52 | }
53 | }
54 |
55 | @Deprecated("This version was a mistake", replaceWith = ReplaceWith("setCustomFont(fontFamily)"))
56 | fun setCustomFont(context: Context, fontFamily: String): Boolean
57 | {
58 | return setCustomFont(fontFamily)
59 | }
60 |
61 | fun setCustomFont(fontFamily: String): Boolean
62 | {
63 | val typeface: Typeface? = FontHelper.getFont(context, fontFamily)
64 | return if (typeface != null)
65 | {
66 | setTypeface(typeface)
67 | true
68 | }
69 | else
70 | {
71 | false
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/TextInputEditTextEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Paint
6 | import android.graphics.Typeface
7 | import android.util.AttributeSet
8 | import com.dg.R
9 | import com.dg.helpers.FontHelper
10 | import com.google.android.material.textfield.TextInputEditText
11 |
12 | @Suppress("unused")
13 | class TextInputEditTextEx : TextInputEditText
14 | {
15 | constructor(context: Context) : super(context)
16 |
17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
18 | {
19 | setCustomFontFamily(context, attrs)
20 | }
21 |
22 | constructor(context: Context,
23 | attrs: AttributeSet,
24 | defStyle: Int) : super(context, attrs, defStyle)
25 | {
26 | setCustomFontFamily(context, attrs)
27 | }
28 |
29 | private fun setCustomFontFamily(context: Context, attrs: AttributeSet)
30 | {
31 | if (isInEditMode)
32 | {
33 | return
34 | }
35 |
36 | val fontFamily: String?
37 | var styledAttributes: TypedArray? = null
38 | try
39 | {
40 | styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.TextInputEditTextEx)
41 | fontFamily = styledAttributes!!.getString(R.styleable.TextInputEditTextEx_customFontFamily)
42 | }
43 | finally
44 | {
45 | styledAttributes?.recycle()
46 | }
47 |
48 | if (fontFamily != null && !fontFamily.isEmpty())
49 | {
50 | paintFlags = this.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
51 | setCustomFont(fontFamily)
52 | }
53 | }
54 |
55 | @Deprecated("This version was a mistake", replaceWith = ReplaceWith("setCustomFont(fontFamily)"))
56 | fun setCustomFont(context: Context, fontFamily: String): Boolean
57 | {
58 | return setCustomFont(fontFamily)
59 | }
60 |
61 | fun setCustomFont(fontFamily: String): Boolean
62 | {
63 | val typeface: Typeface? = FontHelper.getFont(context, fontFamily)
64 | return if (typeface != null)
65 | {
66 | setTypeface(typeface)
67 | true
68 | }
69 | else
70 | {
71 | false
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/DismissSoftkeyboardHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.os.Build
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.view.inputmethod.InputMethodManager
9 | import android.widget.*
10 |
11 | @Suppress("unused")
12 | object DismissSoftkeyboardHelper
13 | {
14 | @Suppress("MemberVisibilityCanBePrivate")
15 | fun setupUI(context: Context, view: View?, deep: Boolean = false)
16 | {
17 | if (view == null) return
18 |
19 | val viewPackageName = view.javaClass.getPackage()?.name
20 | val viewClassName = view.javaClass.simpleName
21 |
22 | if (view is EditText ||
23 | view is Button ||
24 | view is ImageButton ||
25 | view is Checkable ||
26 | view is DatePicker ||
27 | view is AdapterView<*> ||
28 | view is NumberPicker ||
29 | view is RadioGroup ||
30 | view is TimePicker ||
31 | Build.VERSION.SDK_INT >= 21 && view is Toolbar ||
32 | viewPackageName == "android.widget" && viewClassName == "Toolbar" ||
33 | viewClassName == "TextInputLayout")
34 | {
35 | return
36 | }
37 |
38 | view.setOnTouchListener { _, _ ->
39 | hideSoftKeyboard(context)
40 | false
41 | }
42 |
43 | if (deep)
44 | {
45 | if (view is ViewGroup)
46 | {
47 | for (i in 0 until view.childCount)
48 | {
49 | val innerView = view.getChildAt(i)
50 | setupUI(context, innerView)
51 | }
52 | }
53 | }
54 | }
55 |
56 | @Suppress("MemberVisibilityCanBePrivate")
57 | fun hideSoftKeyboard(context: Context)
58 | {
59 | try
60 | {
61 | val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
62 | val currentFocus = (context as Activity).currentFocus
63 | if (currentFocus != null)
64 | {
65 | inputMethodManager.hideSoftInputFromWindow(
66 | currentFocus.windowToken,
67 | InputMethodManager.HIDE_NOT_ALWAYS)
68 | }
69 | }
70 | catch (ex: Exception)
71 | {
72 |
73 | }
74 |
75 | }
76 | }
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/ExecutorHelper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.dg.helpers
18 |
19 | import android.annotation.SuppressLint
20 | import android.os.AsyncTask
21 | import android.os.Build
22 | import java.util.concurrent.*
23 | import java.util.concurrent.atomic.AtomicInteger
24 |
25 | /**
26 | * Utilities for handling Executors
27 | */
28 | @Suppress("unused")
29 | object ExecutorHelper
30 | {
31 | val CPU_COUNT = Runtime.getRuntime().availableProcessors()
32 | val CORE_POOL_SIZE = CPU_COUNT + 1
33 | val MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1
34 | val KEEP_ALIVE = 1
35 |
36 | val threadPoolExecutor: Executor?
37 | @SuppressLint("NewApi")
38 | get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
39 | {
40 | null
41 | }
42 | else AsyncTask.THREAD_POOL_EXECUTOR
43 |
44 | val serialExecutor: Executor?
45 | @SuppressLint("NewApi")
46 | get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
47 | {
48 | null
49 | }
50 | else AsyncTask.SERIAL_EXECUTOR
51 |
52 | @SuppressLint("NewApi")
53 | fun newExecutor(queueCapacity: Int,
54 | threadNamePrefix: String,
55 | corePoolSize: Int,
56 | maximumPoolSize: Int,
57 | keepAliveTime: Long,
58 | unit: TimeUnit): Executor?
59 | {
60 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
61 | {
62 | return null
63 | }
64 |
65 | val poolWorkQueue = LinkedBlockingQueue(queueCapacity)
66 |
67 | val threadFactory = object : ThreadFactory
68 | {
69 | private val mCount = AtomicInteger(1)
70 |
71 | override fun newThread(r: Runnable): Thread
72 | {
73 | return Thread(r, threadNamePrefix + " #" + mCount.getAndIncrement())
74 | }
75 | }
76 |
77 | return ThreadPoolExecutor(corePoolSize,
78 | maximumPoolSize,
79 | keepAliveTime,
80 | unit,
81 | poolWorkQueue,
82 | threadFactory)
83 | }
84 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/DateHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.text.format.DateUtils
4 |
5 | import java.text.ParseException
6 | import java.text.SimpleDateFormat
7 | import java.util.*
8 |
9 | @Suppress("unused")
10 | object DateHelper
11 | {
12 | private val mIso8601 = ThreadLocal()
13 | private val mIso8601_sss = ThreadLocal()
14 |
15 | @Suppress("MemberVisibilityCanBePrivate")
16 | val iso8601: SimpleDateFormat
17 | get()
18 | {
19 | if (mIso8601.get() == null)
20 | {
21 | val utcTimezone = TimeZone.getTimeZone("GMT")
22 | val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
23 | format.calendar = GregorianCalendar.getInstance()
24 | format.timeZone = utcTimezone
25 | mIso8601.set(format)
26 | }
27 | return mIso8601.get()!!
28 | }
29 |
30 | @Suppress("MemberVisibilityCanBePrivate")
31 | val iso8601_sss: SimpleDateFormat
32 | get()
33 | {
34 | if (mIso8601_sss.get() == null)
35 | {
36 | val utcTimezone = TimeZone.getTimeZone("GMT")
37 | val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
38 | format.calendar = GregorianCalendar.getInstance()
39 | format.timeZone = utcTimezone
40 | mIso8601_sss.set(format)
41 | }
42 | return mIso8601_sss.get()!!
43 | }
44 |
45 | // Backwards compatibility
46 | fun utcIsoDateFormatter(): SimpleDateFormat
47 | {
48 | return iso8601
49 | }
50 |
51 | fun withObject(value: Any?): Date?
52 | {
53 | if (value == null) return null
54 | if (value is Date) return value
55 |
56 | try
57 | {
58 | return iso8601_sss.parse(value.toString())
59 | }
60 | catch (e: ParseException)
61 | {
62 | try
63 | {
64 | return iso8601.parse(value.toString())
65 | }
66 | catch (e: ParseException)
67 | {
68 | e.printStackTrace()
69 | }
70 | }
71 |
72 | return null
73 | }
74 |
75 | fun isDateComponentEqualToDate(date1: Date, date2: Date): Boolean
76 | {
77 | val calendar = Calendar.getInstance()
78 |
79 | calendar.time = date1
80 | val year1 = calendar.get(Calendar.YEAR)
81 | val month1 = calendar.get(Calendar.MONTH)
82 | val day1 = calendar.get(Calendar.DATE)
83 | calendar.time = date2
84 | val year2 = calendar.get(Calendar.YEAR)
85 | val month2 = calendar.get(Calendar.MONTH)
86 | val day2 = calendar.get(Calendar.DATE)
87 |
88 | return year1 == year2 && month1 == month2 && day1 == day2
89 | }
90 |
91 | fun isToday(date: Date): Boolean
92 | {
93 | return DateUtils.isToday(date.time)
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/ParcelHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.os.Parcel
4 |
5 | import java.util.Date
6 |
7 | @Suppress("unused")
8 | object ParcelHelper
9 | {
10 | fun writeString(parcel: Parcel, value: String?)
11 | {
12 | parcel.writeByte(if (value == null) 0.toByte() else 1.toByte())
13 | if (value != null)
14 | {
15 | parcel.writeString(value)
16 | }
17 | }
18 |
19 | fun writeBoolean(parcel: Parcel, value: Boolean?)
20 | {
21 | parcel.writeByte(if (value == null) 0.toByte() else 1.toByte())
22 | if (value != null)
23 | {
24 | parcel.writeByte(if (value) 0.toByte() else 1.toByte())
25 | }
26 | }
27 |
28 | fun writeLong(parcel: Parcel, value: Long?)
29 | {
30 | parcel.writeByte(if (value == null) 0.toByte() else 1.toByte())
31 | if (value != null)
32 | {
33 | parcel.writeLong(value)
34 | }
35 | }
36 |
37 | fun writeInt(parcel: Parcel, value: Int?)
38 | {
39 | parcel.writeByte(if (value == null) 0.toByte() else 1.toByte())
40 | if (value != null)
41 | {
42 | parcel.writeInt(value)
43 | }
44 | }
45 |
46 | fun writeDouble(parcel: Parcel, value: Double?)
47 | {
48 | parcel.writeByte(if (value == null) 0.toByte() else 1.toByte())
49 | if (value != null)
50 | {
51 | parcel.writeDouble(value)
52 | }
53 | }
54 |
55 | fun writeFloat(parcel: Parcel, value: Float?)
56 | {
57 | parcel.writeByte(if (value == null) 0.toByte() else 1.toByte())
58 | if (value != null)
59 | {
60 | parcel.writeFloat(value)
61 | }
62 | }
63 |
64 | fun writeDate(parcel: Parcel, value: Date?)
65 | {
66 | parcel.writeByte(if (value == null) 0.toByte() else 1.toByte())
67 | if (value != null)
68 | {
69 | parcel.writeLong(value.time)
70 | }
71 | }
72 |
73 | fun readString(parcel: Parcel): String?
74 | {
75 | return if (parcel.readByte().toInt() == 0) null else parcel.readString()
76 | }
77 |
78 | fun readBoolean(parcel: Parcel): Boolean?
79 | {
80 | return if (parcel.readByte().toInt() == 0) null else parcel.readByte().toInt() == 1
81 | }
82 |
83 | fun readLong(parcel: Parcel): Long?
84 | {
85 | return if (parcel.readByte().toInt() == 0) null else parcel.readLong()
86 | }
87 |
88 | fun readInt(parcel: Parcel): Int?
89 | {
90 | return if (parcel.readByte().toInt() == 0) null else parcel.readInt()
91 | }
92 |
93 | fun readDouble(parcel: Parcel): Double?
94 | {
95 | return if (parcel.readByte().toInt() == 0) null else parcel.readDouble()
96 | }
97 |
98 | fun readFloat(parcel: Parcel): Float?
99 | {
100 | return if (parcel.readByte().toInt() == 0) null else parcel.readFloat()
101 | }
102 |
103 | fun readDate(parcel: Parcel): Date?
104 | {
105 | return if (parcel.readByte().toInt() == 0) null else Date(parcel.readLong())
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/FontHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.content.Context
4 | import android.graphics.Typeface
5 | import android.util.Log
6 |
7 | import java.util.HashMap
8 |
9 | /**
10 | * This loads the specified font from the assets folder.
11 | * If the path specified does not contain path and extension
12 | * - it will search for assets/fonts/.[ttf|otf]
13 | */
14 | @Suppress("unused")
15 | class FontHelper private constructor()
16 | {
17 | companion object
18 | {
19 | private val sFontCache = HashMap()
20 |
21 | fun clearCache()
22 | {
23 | sFontCache.clear()
24 | }
25 |
26 | fun cacheSize(): Int
27 | {
28 | return sFontCache.size
29 | }
30 |
31 | /**
32 | * Loads the font with possible use of the static cache
33 | * @param context
34 | * @param fontFamily
35 | * @param loadFromCache Should the font be loaded from cache?
36 | * @param saveToCache Should the font be saved to cache?
37 | * @return The loaded Typeface or null if failed.
38 | */
39 | fun getFont(context: Context,
40 | fontFamily: String,
41 | loadFromCache: Boolean = true,
42 | saveToCache: Boolean = true): Typeface?
43 | {
44 | var typeface: Typeface? = if (loadFromCache) sFontCache[fontFamily] else null
45 | if (typeface == null)
46 | {
47 | typeface = loadFontFromAssets(context, fontFamily)
48 | if (saveToCache && typeface != null)
49 | {
50 | sFontCache[fontFamily] = typeface
51 | }
52 | }
53 | return typeface
54 | }
55 |
56 | /**
57 | * Loads the font directly from the Assets. No caching done here.
58 | * @param context
59 | * @param fontFamily
60 | * @return The loaded Typeface or null if failed.
61 | */
62 | private fun loadFontFromAssets(context: Context, fontFamily: String): Typeface?
63 | {
64 | if (!fontFamily.contains(".") && !fontFamily.contains("/"))
65 | {
66 | val ttfFontFamily = "fonts/$fontFamily.ttf"
67 |
68 | try
69 | {
70 | return Typeface.createFromAsset(context.resources.assets, ttfFontFamily)
71 | }
72 | catch (ignored: Exception)
73 | {
74 | }
75 |
76 | val otfFontFamily = "fonts/$fontFamily.otf"
77 |
78 | try
79 | {
80 | return Typeface.createFromAsset(context.resources.assets, otfFontFamily)
81 | }
82 | catch (ignored: Exception)
83 | {
84 | }
85 |
86 | }
87 |
88 | try
89 | {
90 | return Typeface.createFromAsset(context.resources.assets, fontFamily)
91 | }
92 | catch (e: Exception)
93 | {
94 | Log.e("FontHelper", e.message ?: "")
95 | return null
96 | }
97 |
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/sonatype-push.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | def isReleaseBuild() {
5 | return !VERSION_NAME.contains("SNAPSHOT")
6 | }
7 |
8 | def getReleaseRepositoryUrl() {
9 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
10 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
11 | }
12 |
13 | def getSnapshotRepositoryUrl() {
14 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
15 | : "https://oss.sonatype.org/content/repositories/snapshots/"
16 | }
17 |
18 | def getRepositoryUsername() {
19 | def localProperties = new Properties()
20 | localProperties.load(new FileInputStream(rootProject.file("local.properties")))
21 | return localProperties.getProperty("sonatypeUsername")
22 | }
23 |
24 | def getRepositoryPassword() {
25 | def localProperties = new Properties()
26 | localProperties.load(new FileInputStream(rootProject.file("local.properties")))
27 | return localProperties.getProperty("sonatypePassword")
28 | }
29 |
30 | afterEvaluate {
31 | publishing {
32 | publications {
33 | release(MavenPublication) {
34 | groupId = GROUP
35 | artifactId = POM_ARTIFACT_ID
36 | version = VERSION_NAME
37 | from components.release
38 |
39 | pom {
40 | name = POM_NAME
41 | packaging = POM_PACKAGING
42 | description = POM_DESCRIPTION
43 | url = POM_URL
44 |
45 | scm {
46 | url = POM_SCM_URL
47 | connection = POM_SCM_CONNECTION
48 | developerConnection = POM_SCM_DEV_CONNECTION
49 | }
50 |
51 | licenses {
52 | license {
53 | name = POM_LICENSE_NAME
54 | url = POM_LICENSE_URL
55 | distribution = POM_LICENSE_DIST
56 | }
57 | }
58 |
59 | developers {
60 | developer {
61 | id = POM_DEVELOPER_ID
62 | name = POM_DEVELOPER_NAME
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | repositories {
70 | maven {
71 | name = 'sonatype'
72 | url = isReleaseBuild() ? getReleaseRepositoryUrl() : getSnapshotRepositoryUrl()
73 |
74 | credentials {
75 | username = getRepositoryUsername()
76 | password = getRepositoryPassword()
77 | }
78 | }
79 | }
80 | }
81 |
82 | signing {
83 | required { isReleaseBuild() && gradle.taskGraph.hasTask("publish") }
84 | sign publishing.publications.release
85 | }
86 |
87 | /*javadoc {
88 | if (JavaVersion.current().isJava9Compatible()) {
89 | options.addBooleanOption('html5', true)
90 | }
91 | }*/
92 | }
93 |
94 | nexusPublishing {
95 | packageGroup = GROUP
96 | repositories {
97 | sonatype {
98 | username = getRepositoryUsername()
99 | password = getRepositoryPassword()
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/animations/PopupWindowResizeAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.dg.animations
2 |
3 | import android.view.animation.Animation
4 | import android.view.animation.Transformation
5 | import android.widget.PopupWindow
6 |
7 | @Suppress("unused")
8 | open class PopupWindowResizeAnimation : Animation
9 | {
10 | private val mPopupWindow: PopupWindow
11 | private var mFromWidth: Int
12 | private var mToWidth: Int
13 | private var mFromHeight: Int
14 | private var mToHeight: Int
15 |
16 | /**
17 | * Constructs a resize animation
18 | * @param popupWindow - the popup to animate
19 | * @param fromWidth - the original width to animate from.
20 | * Use `CURRENT_SIZE` to set from current width automatically.
21 | * Set `fromWidth` and `toWidth` to `CURRENT_SIZE` to disable resizing this axis.
22 | * @param toWidth - the target width to animate to.
23 | * Use `CURRENT_SIZE` to set from current width automatically.
24 | * Set `fromWidth` and `toWidth` to `CURRENT_SIZE` to disable resizing this axis.
25 | * @param fromHeight - the original height to animate from.
26 | * Use `CURRENT_SIZE` to set from current height automatically.
27 | * Set `fromHeight` and `toHeight` to `CURRENT_SIZE` to disable resizing this axis.
28 | * @param toHeight - the target height to animate to.
29 | * Use `CURRENT_SIZE` to set from current height automatically.
30 | * Set `fromHeight` and `toHeight` to `CURRENT_SIZE` to disable resizing this axis.
31 | */
32 | @Suppress("ConvertSecondaryConstructorToPrimary")
33 | constructor(popupWindow: PopupWindow,
34 | fromWidth: Int,
35 | toWidth: Int,
36 | fromHeight: Int,
37 | toHeight: Int) : super()
38 | {
39 | this.mPopupWindow = popupWindow
40 | this.mFromWidth = fromWidth
41 | this.mToWidth = toWidth
42 | this.mFromHeight = fromHeight
43 | this.mToHeight = toHeight
44 | mWidthEnabled = fromWidth != CURRENT_SIZE || toWidth != CURRENT_SIZE
45 | mHeightEnabled = fromHeight != CURRENT_SIZE || toHeight != CURRENT_SIZE
46 | }
47 |
48 | private val mWidthEnabled: Boolean
49 | private val mHeightEnabled: Boolean
50 |
51 | override fun applyTransformation(interpolatedTime: Float, t: Transformation)
52 | {
53 | if (mWidthEnabled)
54 | {
55 | if (mFromWidth == CURRENT_SIZE)
56 | {
57 | mFromWidth = mPopupWindow.width
58 | }
59 | if (mToWidth == CURRENT_SIZE)
60 | {
61 | mToWidth = mPopupWindow.width
62 | }
63 |
64 | mPopupWindow.width = (mFromWidth + (mToWidth - mFromWidth).toFloat() * interpolatedTime).toInt()
65 | }
66 | if (mHeightEnabled)
67 | {
68 | if (mFromHeight == CURRENT_SIZE)
69 | {
70 | mFromHeight = mPopupWindow.height
71 | }
72 | if (mToHeight == CURRENT_SIZE)
73 | {
74 | mToHeight = mPopupWindow.height
75 | }
76 |
77 | mPopupWindow.height = (mFromHeight + (mToHeight - mFromHeight).toFloat() * interpolatedTime).toInt()
78 | }
79 |
80 | mPopupWindow.update(mPopupWindow.width, mPopupWindow.height)
81 | }
82 |
83 | override fun willChangeBounds(): Boolean
84 | {
85 | return true
86 | }
87 |
88 | companion object
89 | {
90 | const val CURRENT_SIZE = -1
91 | }
92 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/animations/ResizeAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.dg.animations
2 |
3 | import android.view.View
4 | import android.view.animation.Animation
5 | import android.view.animation.Transformation
6 |
7 | @Suppress("unused")
8 | open class ResizeAnimation : Animation
9 | {
10 |
11 | private val mAnimatingView: View
12 | private var mFromWidth: Int
13 | private var mToWidth: Int
14 | private var mFromHeight: Int
15 | private var mToHeight: Int
16 |
17 | /**
18 | * Constructs a resize animation
19 | * @param view - the view to animate
20 | * @param fromWidth - the original width to animate from.
21 | * Use `CURRENT_SIZE` to set from current width automatically.
22 | * Set `fromWidth` and `toWidth` to `CURRENT_SIZE` to disable resizing this axis.
23 | * @param toWidth - the target width to animate to.
24 | * Use `CURRENT_SIZE` to set from current width automatically.
25 | * Set `fromWidth` and `toWidth` to `CURRENT_SIZE` to disable resizing this axis.
26 | * @param fromHeight - the original height to animate from.
27 | * Use `CURRENT_SIZE` to set from current height automatically.
28 | * Set `fromHeight` and `toHeight` to `CURRENT_SIZE` to disable resizing this axis.
29 | * @param toHeight - the target height to animate to.
30 | * Use `CURRENT_SIZE` to set from current height automatically.
31 | * Set `fromHeight` and `toHeight` to `CURRENT_SIZE` to disable resizing this axis.
32 | */
33 | @Suppress("ConvertSecondaryConstructorToPrimary")
34 | constructor(view: View,
35 | fromWidth: Int,
36 | toWidth: Int,
37 | fromHeight: Int,
38 | toHeight: Int) : super()
39 | {
40 | this.mAnimatingView = view
41 | this.mFromWidth = fromWidth
42 | this.mToWidth = toWidth
43 | this.mFromHeight = fromHeight
44 | this.mToHeight = toHeight
45 | mWidthEnabled = fromWidth != CURRENT_SIZE || toWidth != CURRENT_SIZE
46 | mHeightEnabled = fromHeight != CURRENT_SIZE || toHeight != CURRENT_SIZE
47 | }
48 |
49 | private val mWidthEnabled: Boolean
50 | private val mHeightEnabled: Boolean
51 |
52 | override fun applyTransformation(interpolatedTime: Float, t: Transformation)
53 | {
54 | val params = mAnimatingView.layoutParams
55 |
56 | if (mWidthEnabled)
57 | {
58 | if (mFromWidth == CURRENT_SIZE)
59 | {
60 | mFromWidth = mAnimatingView.measuredWidth
61 | }
62 | if (mToWidth == CURRENT_SIZE)
63 | {
64 | mToWidth = mAnimatingView.measuredWidth
65 | }
66 |
67 | params.width = (mFromWidth + (mToWidth - mFromWidth).toFloat() * interpolatedTime).toInt()
68 | }
69 | if (mHeightEnabled)
70 | {
71 | if (mFromHeight == CURRENT_SIZE)
72 | {
73 | mFromHeight = mAnimatingView.measuredHeight
74 | }
75 | if (mToHeight == CURRENT_SIZE)
76 | {
77 | mToHeight = mAnimatingView.measuredHeight
78 | }
79 |
80 | params.height = (mFromHeight + (mToHeight - mFromHeight).toFloat() * interpolatedTime).toInt()
81 | }
82 |
83 | mAnimatingView.requestLayout()
84 | }
85 |
86 | override fun willChangeBounds(): Boolean
87 | {
88 | return true
89 | }
90 |
91 | companion object
92 | {
93 | const val CURRENT_SIZE = -1
94 | }
95 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Helpers
2 | =======
3 |
4 | This is a collection of helper classes for Android.
5 |
6 | Features:
7 |
8 | * Helpers to "parse" `Boolean` / `Int` / `Long` / `String` / `Date` / `Double` / `BigDecimal` types. This is great for reading from a `Map`, or for example parsing a `Date` field from a `JSONObject`'s property.
9 | * Helpers for handling bitmaps (i.e. EXIF orientation)
10 | * Helpers for Executor objects
11 | * Helpers for loading custom fonts from the assets folder
12 | * Helpers for working with `JSONObject` and `JSONArray`
13 | * Helpers for working with locations
14 | * Helpers for working with Parcels. Mainly encoding/decoding more safely, preserving `null` status of properties.
15 | * Helpers for working with Views (i.e. cause a re-layout *now* instead of in the next ui thread loop)
16 | * `ActivityResultHelper` for simplifying starting activities with result codes (No more spaghetti code in MainActivity...)
17 | * `RuntimePermissionHelper` for simplifying permission requests on Android 6.0 (Marshmallow)
18 | * `FragmentTransitionHelper` for acquiring default transition animation for `Fragment`s
19 | * `SpannableStringBuilderEx` with more help adding spannables
20 | * `CustomTypefaceSpannable` for having a SpannableString with real custom TypeFaces
21 | * `SchedulerHandler` - a `Handler` with scheduling methods
22 | * Controls that support custom typefaces for (`ButtonEx`, `EditTextEx`, `TextViewEx`)
23 | * A `SwipeRefreshLayout` subclass that makes `setRefreshing(...)` more safe, deferring until after `onMeasure` is called.
24 | * Animations for *resizing* a `View` or a `PopupView`, instead of relative scaling
25 | * More stuff! Just explore tha package...
26 |
27 | Have fun and a good luck with your projects!
28 |
29 | ## Other projects
30 |
31 | Checkout [httprequest](https://github.com/danielgindi/android-httprequest) for working with Http request/response easily and without memory limits
32 |
33 | ## Dependency
34 |
35 | [Download from Maven Central (.aar)](https://oss.sonatype.org/index.html#view-repositories;releases~browsestorage~/com/github/danielgindi/helpers/1.3.1/helpers-1.3.1.aar)
36 |
37 | **or**
38 |
39 | ```java
40 | dependencies {
41 | compile 'com.github.danielgindi:helpers:1.3.3'
42 | }
43 | ```
44 |
45 | ## License
46 |
47 | All the code here is under MIT license. Which means you could do virtually anything with the code.
48 | I will appreciate it very much if you keep an attribution where appropriate.
49 |
50 | The MIT License (MIT)
51 |
52 | Copyright (c) 2013 Daniel Cohen Gindi (danielgindi@gmail.com)
53 |
54 | Permission is hereby granted, free of charge, to any person obtaining a copy
55 | of this software and associated documentation files (the "Software"), to deal
56 | in the Software without restriction, including without limitation the rights
57 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
58 | copies of the Software, and to permit persons to whom the Software is
59 | furnished to do so, subject to the following conditions:
60 |
61 | The above copyright notice and this permission notice shall be included in all
62 | copies or substantial portions of the Software.
63 |
64 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
65 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
66 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
67 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
68 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
69 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
70 | SOFTWARE.
71 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/EditTextEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Paint
6 | import android.graphics.Typeface
7 | import android.os.Build
8 | import android.util.AttributeSet
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.view.ViewParent
12 | import androidx.appcompat.widget.AppCompatEditText
13 | import com.dg.R
14 | import com.dg.helpers.FontHelper
15 |
16 | @Suppress("unused")
17 | class EditTextEx : AppCompatEditText
18 | {
19 | private val mCanFocusZeroSized = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
20 |
21 | constructor(context: Context) : super(context)
22 |
23 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
24 | {
25 | setCustomFontFamily(context, attrs)
26 | }
27 |
28 | constructor(context: Context,
29 | attrs: AttributeSet,
30 | defStyle: Int) : super(context, attrs, defStyle)
31 | {
32 | setCustomFontFamily(context, attrs)
33 | }
34 |
35 | private fun setCustomFontFamily(context: Context, attrs: AttributeSet)
36 | {
37 | if (isInEditMode)
38 | {
39 | return
40 | }
41 |
42 | val fontFamily: String?
43 | var styledAttributes: TypedArray? = null
44 | try
45 | {
46 | styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.EditTextEx)
47 | fontFamily = styledAttributes!!.getString(R.styleable.EditTextEx_customFontFamily)
48 | }
49 | finally
50 | {
51 | styledAttributes?.recycle()
52 | }
53 |
54 | if (fontFamily != null && !fontFamily.isEmpty())
55 | {
56 | paintFlags = this.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
57 | setCustomFont(fontFamily)
58 | }
59 | }
60 |
61 | @Deprecated("This version was a mistake", replaceWith = ReplaceWith("setCustomFont(fontFamily)"))
62 | fun setCustomFont(context: Context, fontFamily: String): Boolean
63 | {
64 | return setCustomFont(fontFamily)
65 | }
66 |
67 | fun setCustomFont(fontFamily: String): Boolean
68 | {
69 | val typeface: Typeface? = FontHelper.getFont(context, fontFamily)
70 | return if (typeface != null)
71 | {
72 | setTypeface(typeface)
73 | true
74 | }
75 | else
76 | {
77 | false
78 | }
79 | }
80 |
81 | /*
82 | * This is a workaround for a bug in Android where onKeyUp throws if requestFocus() returns false
83 | */
84 | override fun focusSearch(direction: Int): View?
85 | {
86 | val view = super.focusSearch(direction) ?: return null
87 |
88 | if (view.visibility != View.VISIBLE)
89 | return null
90 | if (!view.isFocusable)
91 | return null
92 | if (!view.isEnabled)
93 | return null
94 | if (!mCanFocusZeroSized && (view.bottom <= view.top || view.right <= view.left))
95 | return null
96 | if (view.isInTouchMode && !view.isFocusableInTouchMode)
97 | return null
98 |
99 | var ancestor = view.parent
100 | while (ancestor is ViewGroup)
101 | {
102 | val vgAncestor = ancestor
103 | if (vgAncestor.descendantFocusability == ViewGroup.FOCUS_BLOCK_DESCENDANTS)
104 | return null
105 |
106 | ancestor = vgAncestor.parent
107 | }
108 |
109 | return view
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/TeaEncryptor.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import java.io.UnsupportedEncodingException
4 | import java.nio.charset.Charset
5 | import java.util.ArrayList
6 |
7 | import android.util.Base64
8 | import android.util.Log
9 | import kotlin.math.ceil
10 |
11 | @Suppress("unused")
12 | object TeaEncryptor
13 | {
14 | private fun bytesToLongs(s: ByteArray): MutableList
15 | {
16 | val slen = s.size
17 | val len = ceil(slen / 4.0).toInt()
18 | val l = ArrayList(len)
19 | var ll: Int
20 | var lll: Int
21 | for (i in 0 until len)
22 | {
23 | lll = 0
24 | ll = i * 4
25 | if (ll < slen) lll += s[ll].toInt()
26 | ll = i * 4 + 1
27 | if (ll < slen) lll += s[ll].toInt() shl 8
28 | ll = i * 4 + 2
29 | if (ll < slen) lll += s[ll].toInt() shl 16
30 | ll = i * 4 + 3
31 | if (ll < slen) lll += s[ll].toInt() shl 24
32 | l.add(lll)
33 | }
34 | return l
35 | }
36 |
37 | private fun longsToBytes(l: List): ByteArray
38 | {
39 | val a = ByteArray(l.size * 4)
40 | var ll: Int
41 | var i = 0
42 | var j = 0
43 | val len = l.size
44 | while (i < len)
45 | {
46 | ll = l[i]
47 | a[j++] = (ll and 0xFF).toByte()
48 | a[j++] = (ll shr 8 and 0xFF).toByte()
49 | a[j++] = (ll shr 16 and 0xFF).toByte()
50 | a[j++] = (ll shr 24 and 0xFF).toByte()
51 | i++
52 | }
53 | return a
54 | }
55 |
56 | private const val DELTA = -0x61c88647
57 |
58 | fun encrypt(plaintext: String, password: String): String
59 | {
60 | if (plaintext.isEmpty()) return ""
61 | val v = bytesToLongs(plaintext.toByteArray(Charset.forName("UTF-8")))
62 | while (v.size <= 1) v.add(0)
63 | val k = bytesToLongs(password.toByteArray(Charset.forName("UTF-8")))
64 | while (k.size < 4) k.add(0)
65 | val n = v.size
66 |
67 | var z = v[n - 1]
68 | var y: Int
69 | var sum = 0
70 | var e: Int
71 | var mx: Int
72 | var q: Int
73 | q = 6 + 52 / n
74 | while (q-- > 0)
75 | {
76 | sum += DELTA
77 | e = sum shr 2 and 3
78 | for (p in 0 until n)
79 | {
80 | y = v[(p + 1) % n]
81 | mx = (z.ushr(5) xor (y shl 2)) + (y.ushr(3) xor (z shl 4)) xor (sum xor y) + (k[p and 3 xor e] xor z)
82 | v[p] = v[p] + mx
83 | z = v[p]
84 | }
85 | }
86 | return Base64.encodeToString(longsToBytes(v), Base64.DEFAULT)
87 | }
88 |
89 | fun decrypt(ciphertext: String, password: String): String
90 | {
91 | if (ciphertext.isEmpty()) return ""
92 | val v = bytesToLongs(Base64.decode(ciphertext, Base64.DEFAULT))
93 | val k = bytesToLongs(password.toByteArray(Charset.forName("UTF-8")))
94 | while (k.size < 4) k.add(0)
95 | val n = v.size
96 |
97 | var z: Int
98 | var y = v[0]
99 | var sum: Int
100 | var e: Int
101 | var mx: Int
102 | val q: Int
103 | q = 6 + 52 / n
104 | sum = q * DELTA
105 |
106 | while (sum != 0)
107 | {
108 | e = sum shr 2 and 3
109 | for (p in n - 1 downTo 0)
110 | {
111 | z = v[if (p > 0) p - 1 else n - 1]
112 | mx = (z.ushr(5) xor (y shl 2)) + (y.ushr(3) xor (z shl 4)) xor (sum xor y) + (k[p and 3 xor e] xor z)
113 | v[p] = v[p] - mx
114 | y = v[p]
115 | }
116 | sum -= DELTA
117 | }
118 |
119 | v.add(0) // null terminator
120 | val plaintext = longsToBytes(v)
121 | try
122 | {
123 | return String(plaintext, Charset.forName("UTF-8"))
124 | }
125 | catch (ex: UnsupportedEncodingException)
126 | {
127 | Log.i("HREQ", "Exception: $ex")
128 | return ""
129 | }
130 |
131 | }
132 |
133 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/controls/InfiniteScrollingListView.kt:
--------------------------------------------------------------------------------
1 | package com.dg.controls
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.Gravity
6 | import android.view.View
7 | import android.widget.AbsListView
8 | import android.widget.LinearLayout
9 | import android.widget.ListView
10 | import android.widget.ProgressBar
11 |
12 | @Suppress("unused")
13 | class InfiniteScrollingListView : ListView
14 | {
15 | private var mIsLoadingMore: Boolean = false
16 | private var mSuspendInfiniteScroll: Boolean = false
17 | private var mProgressBar: ProgressBar? = null
18 | private var mInfiniteScrollActionHandler: InfiniteScrollActionHandler? = null
19 | private var mInfiniteScrollFooter: LinearLayout? = null
20 |
21 | constructor(context: Context) : super(context)
22 |
23 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
24 |
25 | constructor(context: Context,
26 | attrs: AttributeSet,
27 | defStyle: Int) : super(context, attrs, defStyle)
28 |
29 | fun infiniteScrollLoadCompleted()
30 | {
31 | mIsLoadingMore = false
32 | if (mProgressBar != null)
33 | {
34 | mProgressBar!!.visibility = View.GONE
35 | }
36 | }
37 |
38 | fun suspendInfiniteScroll(suspend: Boolean)
39 | {
40 | mSuspendInfiniteScroll = suspend
41 | }
42 |
43 | fun addInfiniteScrollingWithActionHandler(infiniteScrollActionHandler: InfiniteScrollActionHandler)
44 | {
45 | mIsLoadingMore = false
46 |
47 | this.mInfiniteScrollActionHandler = infiniteScrollActionHandler
48 |
49 | if (mProgressBar == null)
50 | {
51 | mProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleSmall)
52 | val progressBarParams = LinearLayout.LayoutParams(
53 | LinearLayout.LayoutParams.WRAP_CONTENT,
54 | LinearLayout.LayoutParams.WRAP_CONTENT)
55 | mProgressBar!!.layoutParams = progressBarParams
56 | mProgressBar!!.setPadding(6, 6, 6, 6)
57 |
58 | mInfiniteScrollFooter = LinearLayout(context)
59 | val layoutParams = LayoutParams(
60 | LinearLayout.LayoutParams.MATCH_PARENT,
61 | LinearLayout.LayoutParams.WRAP_CONTENT)
62 | mInfiniteScrollFooter!!.gravity = Gravity.CENTER
63 | mInfiniteScrollFooter!!.layoutParams = layoutParams
64 | mInfiniteScrollFooter!!.addView(mProgressBar)
65 |
66 | mProgressBar!!.visibility = View.GONE
67 |
68 | addFooterView(mInfiniteScrollFooter)
69 | }
70 |
71 | setOnScrollListener(object : OnScrollListener
72 | {
73 | override fun onScrollStateChanged(view: AbsListView, scrollState: Int)
74 | {
75 |
76 | }
77 |
78 | override fun onScroll(view: AbsListView,
79 | firstVisibleItem: Int,
80 | visibleItemCount: Int,
81 | totalItemCount: Int)
82 | {
83 |
84 | if (mSuspendInfiniteScroll) return
85 |
86 | val loadMore: Boolean = 0 != totalItemCount && firstVisibleItem + visibleItemCount >= totalItemCount
87 |
88 | if (!mIsLoadingMore && loadMore && null != this@InfiniteScrollingListView.mInfiniteScrollActionHandler)
89 | {
90 | if (this@InfiniteScrollingListView.mInfiniteScrollActionHandler!!.infiniteScrollAction())
91 | {
92 | mIsLoadingMore = true
93 | mProgressBar!!.visibility = View.VISIBLE
94 | }
95 | }
96 |
97 | }
98 | })
99 | }
100 |
101 | fun removeInfiniteScrolling()
102 | {
103 | if (mInfiniteScrollFooter != null)
104 | {
105 | removeFooterView(mInfiniteScrollFooter)
106 | }
107 | mInfiniteScrollFooter = null
108 | mProgressBar = null
109 | mInfiniteScrollActionHandler = null
110 | }
111 |
112 | interface InfiniteScrollActionHandler
113 | {
114 | fun infiniteScrollAction(): Boolean
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/ConnectivityHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.net.ConnectivityManager
6 | import android.net.NetworkInfo
7 | import android.telephony.TelephonyManager
8 |
9 | /**
10 | * Check device's network connectivity and speed
11 | * @author emil http://stackoverflow.com/users/220710/emil
12 | */
13 | @Suppress("unused")
14 | object ConnectivityHelper
15 | {
16 |
17 | /**
18 | * Get the network info
19 | * @param context
20 | * @return
21 | */
22 | @SuppressLint("MissingPermission")
23 | fun getNetworkInfo(context: Context): NetworkInfo?
24 | {
25 | val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
26 | return cm.activeNetworkInfo
27 | }
28 |
29 | /**
30 | * Check if there is any connectivity
31 | * @param context
32 | * @return
33 | */
34 | fun isConnected(context: Context): Boolean
35 | {
36 | val info = getNetworkInfo(context)
37 | return info != null && info.isConnected
38 | }
39 |
40 | /**
41 | * Check if there is any connectivity to a Wifi network
42 | * @param context
43 | * @return
44 | */
45 | fun isConnectedWifi(context: Context): Boolean
46 | {
47 | val info = getNetworkInfo(context)
48 | return info != null && info.isConnected && info.type == ConnectivityManager.TYPE_WIFI
49 | }
50 |
51 | /**
52 | * Check if there is any connectivity to a mobile network
53 | * @param context
54 | * @return
55 | */
56 | fun isConnectedMobile(context: Context): Boolean
57 | {
58 | val info = getNetworkInfo(context)
59 | return info != null && info.isConnected && info.type == ConnectivityManager.TYPE_MOBILE
60 | }
61 |
62 | /**
63 | * Check if there is fast connectivity
64 | * @param context
65 | * @return
66 | */
67 | fun isConnectedFast(context: Context): Boolean
68 | {
69 | val info = getNetworkInfo(context)
70 | val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
71 | return info != null && info.isConnected && isConnectionFast(info.type, tm.networkType)
72 | }
73 |
74 | /**
75 | * Check if the connection is fast
76 | * @param type
77 | * @param subType
78 | * @return
79 | */
80 | fun isConnectionFast(type: Int, subType: Int): Boolean
81 | {
82 | return when (type)
83 | {
84 | ConnectivityManager.TYPE_WIFI -> true
85 | ConnectivityManager.TYPE_MOBILE -> when (subType)
86 | {
87 | TelephonyManager.NETWORK_TYPE_1xRTT -> false // ~ 50-100 kbps
88 | TelephonyManager.NETWORK_TYPE_CDMA -> false // ~ 14-64 kbps
89 | TelephonyManager.NETWORK_TYPE_EDGE -> false // ~ 50-100 kbps
90 | TelephonyManager.NETWORK_TYPE_EVDO_0 -> true // ~ 400-1000 kbps
91 | TelephonyManager.NETWORK_TYPE_EVDO_A -> true // ~ 600-1400 kbps
92 | TelephonyManager.NETWORK_TYPE_GPRS -> false // ~ 100 kbps
93 | TelephonyManager.NETWORK_TYPE_HSDPA -> true // ~ 2-14 Mbps
94 | TelephonyManager.NETWORK_TYPE_HSPA -> true // ~ 700-1700 kbps
95 | TelephonyManager.NETWORK_TYPE_HSUPA -> true // ~ 1-23 Mbps
96 | TelephonyManager.NETWORK_TYPE_UMTS -> true // ~ 400-7000 kbps
97 |
98 | /*
99 | * Above API level 7, make sure to set android:targetSdkVersion
100 | * to appropriate level to use these
101 | */
102 | // API level 11
103 | TelephonyManager.NETWORK_TYPE_EHRPD -> true // ~ 1-2 Mbps
104 | // API level 9
105 | TelephonyManager.NETWORK_TYPE_EVDO_B -> true // ~ 5 Mbps
106 | // API level 13
107 | TelephonyManager.NETWORK_TYPE_HSPAP -> true // ~ 10-20 Mbps
108 | // API level 8
109 | TelephonyManager.NETWORK_TYPE_IDEN -> false // ~25 kbps
110 | // API level 11
111 | TelephonyManager.NETWORK_TYPE_LTE -> true // ~ 10+ Mbps
112 | // Unknown
113 | TelephonyManager.NETWORK_TYPE_UNKNOWN -> false
114 | else -> false
115 | }
116 | else -> false
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/spannable/SpannableStringBuilderEx.kt:
--------------------------------------------------------------------------------
1 | package com.dg.spannable
2 |
3 | import android.text.Spannable
4 | import android.text.SpannableStringBuilder
5 | import android.text.Spanned
6 |
7 | @Suppress("unused")
8 | class SpannableStringBuilderEx : SpannableStringBuilder
9 | {
10 | /**
11 | * Create a new SpannableStringBuilderEx with empty contents
12 | */
13 | constructor() : super()
14 |
15 | /**
16 | * Create a new SpannableStringBuilderEx containing a copy of the
17 | * specified text, including its spans if any.
18 | */
19 | constructor(text: CharSequence) : super(text)
20 |
21 | /**
22 | * Create a new SpannableStringBuilderEx containing a copy of the
23 | * specified slice of the specified text, including its spans if any.
24 | */
25 | constructor(text: CharSequence, start: Int, end: Int) : super(text, start, end)
26 |
27 | /**
28 | * Appends the character sequence `text` and spans `what` over the appended part.
29 | * See [Spanned] for an explanation of what the flags mean.
30 | * @param text the character sequence to append.
31 | * @param what the object to be spanned over the appended text.
32 | * @param flags see [Spanned].
33 | * @return this `SpannableStringBuilderEx`.
34 | */
35 | override fun append(text: CharSequence, what: Any, flags: Int): SpannableStringBuilder
36 | {
37 | val start = length
38 | append(text)
39 | setSpan(what, start, length, flags)
40 | return this
41 | }
42 |
43 | /**
44 | * Appends the character sequence `text` and spans `spannables` over the appended part.
45 | * @param text the character sequence to append.
46 | * @param spannables a collection of spannables and their flags to be applied to this text.
47 | * @return this `SpannableStringBuilderEx`.
48 | */
49 | fun append(text: CharSequence, spannables: Array?): SpannableStringBuilder
50 | {
51 | val start = length
52 | append(text)
53 | if (spannables != null)
54 | {
55 | for (spannable in spannables)
56 | {
57 | setSpan(spannable.spannable, start, length, spannable.flags)
58 | }
59 | }
60 | return this
61 | }
62 |
63 | /**
64 | * Appends the character sequence `text` and spans `what` over the appended part.
65 | * See [Spanned] for an explanation of what the flags mean.
66 | * @param where the position to insert at.
67 | * @param text the character sequence to append.
68 | * @param what the object to be spanned over the appended text.
69 | * @param flags see [Spanned].
70 | * @return this `SpannableStringBuilderEx`.
71 | */
72 | fun insert(where: Int, text: CharSequence, what: Any, flags: Int): SpannableStringBuilder
73 | {
74 | insert(where, text)
75 | setSpan(what, where, where + text.length, flags)
76 | return this
77 | }
78 |
79 | /**
80 | * Inserts the character sequence `text` and spans `spannables` over the appended part.
81 | * @param where the position to insert at.
82 | * @param text the character sequence to append.
83 | * @param spannables a collection of spannables and their flags to be applied to this text.
84 | * @return this `SpannableStringBuilderEx`.
85 | */
86 | fun insert(where: Int, text: CharSequence, spannables: Array?): SpannableStringBuilder
87 | {
88 | insert(where, text)
89 | if (spannables != null)
90 | {
91 | for (spannable in spannables)
92 | {
93 | setSpan(spannable.spannable, where, where + text.length, spannable.flags)
94 | }
95 | }
96 | return this
97 | }
98 |
99 | /**
100 | * This class wraps a pair of a Spannable and Flags for spanning.
101 | */
102 | class SpannableWithFlags
103 | {
104 | var spannable: Any? = null
105 | var flags: Int = 0
106 |
107 | /**
108 | * Constructs a pair of a Spannable with SPAN_INCLUSIVE_INCLUSIVE flags.
109 | * @param spannable the object to be spanned over the appended text.
110 | */
111 | constructor(spannable: Any)
112 | {
113 | this.spannable = spannable
114 | this.flags = Spannable.SPAN_INCLUSIVE_INCLUSIVE
115 | }
116 |
117 | /**
118 | * Constructs a pair of a Spannable with flags.
119 | * @param spannable the object to be spanned over the appended text.
120 | * @param flags see [Spanned].
121 | */
122 | constructor(spannable: Any, flags: Int)
123 | {
124 | this.spannable = spannable
125 | this.flags = flags
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/DbHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import android.content.ContentValues
4 | import android.database.sqlite.SQLiteDatabase
5 | import java.text.ParseException
6 | import java.text.SimpleDateFormat
7 | import java.util.*
8 |
9 | @Suppress("unused")
10 | object DbHelper
11 | {
12 | fun utcIsoDateFormatter(): SimpleDateFormat
13 | {
14 | return DateHelper.utcIsoDateFormatter()
15 | }
16 |
17 | @Suppress("MemberVisibilityCanBePrivate")
18 | fun getDateForDbFromDate(date: Date?, escape: Boolean): String?
19 | {
20 | return if (date != null)
21 | {
22 | if (escape)
23 | {
24 | "'" + DateHelper.utcIsoDateFormatter().format(date) + "'"
25 | }
26 | else
27 | {
28 | DateHelper.utcIsoDateFormatter().format(date)
29 | }
30 | }
31 | else
32 | {
33 | if (escape)
34 | {
35 | "null"
36 | }
37 | else
38 | {
39 | null
40 | }
41 | }
42 | }
43 |
44 | fun getDateFromDbString(date: String?): Date?
45 | {
46 | if (date == null)
47 | {
48 | return null
49 | }
50 | try
51 | {
52 | return DateHelper.utcIsoDateFormatter().parse(date)
53 | }
54 | catch (ignored: ParseException)
55 | {
56 | return null
57 | }
58 |
59 | }
60 |
61 | fun queryScalarFloat(database: SQLiteDatabase, sql: String): Float?
62 | {
63 | val cursor = database.rawQuery(sql, null)
64 | if (cursor.moveToFirst())
65 | {
66 | val `val` = if (cursor.isNull(0)) null else cursor.getFloat(0)
67 | cursor.close()
68 | return `val`
69 | }
70 | cursor.close()
71 | return null
72 | }
73 |
74 | fun queryScalarInt(database: SQLiteDatabase, sql: String): Int?
75 | {
76 | val cursor = database.rawQuery(sql, null)
77 | if (cursor.moveToFirst())
78 | {
79 | val `val` = if (cursor.isNull(0)) null else cursor.getInt(0)
80 | cursor.close()
81 | return `val`
82 | }
83 | cursor.close()
84 | return null
85 | }
86 |
87 | fun queryScalarLong(database: SQLiteDatabase, sql: String): Long?
88 | {
89 | val cursor = database.rawQuery(sql, null)
90 | if (cursor.moveToFirst())
91 | {
92 | val `val` = if (cursor.isNull(0)) null else cursor.getLong(0)
93 | cursor.close()
94 | return `val`
95 | }
96 | cursor.close()
97 | return null
98 | }
99 |
100 | fun queryScalarDouble(database: SQLiteDatabase, sql: String): Double?
101 | {
102 | val cursor = database.rawQuery(sql, null)
103 | if (cursor.moveToFirst())
104 | {
105 | val `val` = if (cursor.isNull(0)) null else cursor.getDouble(0)
106 | cursor.close()
107 | return `val`
108 | }
109 | cursor.close()
110 | return null
111 | }
112 |
113 | fun queryScalarShort(database: SQLiteDatabase, sql: String): Short?
114 | {
115 | val cursor = database.rawQuery(sql, null)
116 | if (cursor.moveToFirst())
117 | {
118 | val `val` = if (cursor.isNull(0)) null else cursor.getShort(0)
119 | cursor.close()
120 | return `val`
121 | }
122 | cursor.close()
123 | return null
124 | }
125 |
126 | fun queryScalarBoolean(database: SQLiteDatabase, sql: String): Boolean?
127 | {
128 | val cursor = database.rawQuery(sql, null)
129 | if (cursor.moveToFirst())
130 | {
131 | val `val` = if (cursor.isNull(0)) null else cursor.getInt(0) != 0
132 | cursor.close()
133 | return `val`
134 | }
135 | cursor.close()
136 | return null
137 | }
138 |
139 | fun queryScalarString(database: SQLiteDatabase, sql: String): String?
140 | {
141 | val cursor = database.rawQuery(sql, null)
142 | if (cursor.moveToFirst())
143 | {
144 | val `val` = if (cursor.isNull(0)) null else cursor.getString(0)
145 | cursor.close()
146 | return `val`
147 | }
148 | cursor.close()
149 | return null
150 | }
151 |
152 | fun getContentValuesForMap(values: Map): ContentValues
153 | {
154 | val content = ContentValues()
155 |
156 | for (key in values.keys)
157 | {
158 | when (val value = values[key])
159 | {
160 | null -> content.putNull(key)
161 | is Int -> content.put(key, value)
162 | is Short -> content.put(key, value)
163 | is Long -> content.put(key, value)
164 | is Float -> content.put(key, value)
165 | is Double -> content.put(key, value)
166 | is Boolean -> content.put(key, value)
167 | is String -> content.put(key, value)
168 | is Date -> content.put(key, getDateForDbFromDate(value, false))
169 | }
170 | }
171 |
172 | return content
173 | }
174 |
175 | fun escape(value: String): String
176 | {
177 | return value.replace("'", "''")
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/JSONHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import org.json.JSONArray
4 | import org.json.JSONException
5 | import org.json.JSONObject
6 |
7 | import java.util.ArrayList
8 | import java.util.HashMap
9 |
10 | @Suppress("unused")
11 | object JSONHelper
12 | {
13 | fun toStringArray(json: JSONArray?): Array?
14 | {
15 | if (json == null) return null
16 |
17 | val length = json.length()
18 | val array = arrayOfNulls(length)
19 |
20 | for (i in 0 until length)
21 | {
22 | val value = json.opt(i)
23 | array[i] = if (value == null) null else value as? String ?: value.toString()
24 | }
25 |
26 | return array
27 | }
28 |
29 | fun toArray(json: JSONArray?): ArrayList<*>?
30 | {
31 | if (json == null) return null
32 |
33 | val length = json.length()
34 | val array = ArrayList(length)
35 |
36 | for (i in 0 until length)
37 | {
38 | val value = json.opt(i)
39 | when
40 | {
41 | value is JSONArray -> array.add(toArray(value))
42 | value === JSONObject.NULL -> array.add(null)
43 | value is JSONObject -> array.add(toMap(value))
44 | else -> array.add(value)
45 | }
46 | }
47 |
48 | return array
49 | }
50 |
51 | inline fun toArray(list: List<*>): Array
52 | {
53 | @Suppress("UNCHECKED_CAST")
54 | return (list as List).toTypedArray()
55 | }
56 |
57 | inline fun toArray(json: JSONArray?): Array?
58 | {
59 | if (json == null) return null
60 |
61 | val length = json.length()
62 | val array = ArrayList(length)
63 |
64 | for (i in 0 until length)
65 | {
66 | val value = json.opt(i)
67 | when
68 | {
69 | value is JSONArray -> array.add(toArray(value))
70 | value === JSONObject.NULL -> array.add(null)
71 | value is JSONObject -> array.add(toMap(value))
72 | else -> array.add(value)
73 | }
74 | }
75 |
76 | return toArray(array)
77 | }
78 |
79 | fun toMap(json: JSONObject?): Map?
80 | {
81 | if (json == null) return null
82 |
83 | val map = HashMap(json.length())
84 |
85 | val keys = json.keys()
86 | while (keys.hasNext())
87 | {
88 | val name = keys.next()
89 |
90 | val value = json.opt(name)
91 | when
92 | {
93 | value is JSONArray -> map[name] = toArray(value)
94 | value === JSONObject.NULL -> map[name] = null
95 | value is JSONObject -> map[name] = toMap(value)
96 | else -> map[name] = value
97 | }
98 | }
99 |
100 | return map
101 | }
102 |
103 | /**
104 | * Wraps the given object if necessary.
105 | *
106 | *
107 | * If the object is null, returns `JSONObject.NULL`.
108 | * If the object is a `JSONArray` or `JSONObject`, no wrapping is necessary.
109 | * If the object is `JSONObject.NULL`, no wrapping is necessary.
110 | * If the object is an array or `Collection`, returns an equivalent `JSONArray`.
111 | * If the object is a `Map`, returns an equivalent `JSONObject`.
112 | * If the object is a primitive wrapper type or `String`, returns the object.
113 | * Otherwise if the object is from a `java` package, returns the result of `toString`.
114 | * If wrapping fails, returns null.
115 | */
116 | @Suppress("MemberVisibilityCanBePrivate")
117 | fun wrap(o: Any?): Any?
118 | {
119 | if (o == null)
120 | {
121 | return JSONObject.NULL
122 | }
123 |
124 | if (o is JSONArray || o is JSONObject)
125 | {
126 | return o
127 | }
128 |
129 | if (o == JSONObject.NULL)
130 | {
131 | return o
132 | }
133 |
134 | try
135 | {
136 | if (o is Collection<*>)
137 | {
138 | val jsonArray = JSONArray()
139 | for (entry in (o as Collection<*>?)!!)
140 | {
141 | jsonArray.put(wrap(entry))
142 | }
143 | return jsonArray
144 | }
145 |
146 | if (o.javaClass.isArray)
147 | {
148 | val length = java.lang.reflect.Array.getLength(o)
149 | val jsonArray = JSONArray()
150 | for (i in 0 until length)
151 | {
152 | jsonArray.put(wrap(java.lang.reflect.Array.get(o, i)))
153 | }
154 |
155 | return jsonArray
156 | }
157 |
158 | if (o is Map<*, *>)
159 | {
160 | val `object` = JSONObject()
161 |
162 | for ((key1, value) in o)
163 | {
164 | val key = key1 as? String ?: key1.toString()
165 |
166 | try
167 | {
168 | `object`.put(key, wrap(value))
169 | }
170 | catch (e: JSONException)
171 | {
172 | e.printStackTrace()
173 | }
174 |
175 | }
176 |
177 | return `object`
178 | }
179 |
180 | if (o is Boolean ||
181 | o is Byte ||
182 | o is Char ||
183 | o is Double ||
184 | o is Float ||
185 | o is Int ||
186 | o is Long ||
187 | o is Short ||
188 | o is String)
189 | {
190 | return o
191 | }
192 |
193 | if (o.javaClass.getPackage()?.name?.startsWith("java.") == true)
194 | {
195 | return o.toString()
196 | }
197 |
198 | }
199 | catch (ignored: Exception)
200 | {
201 | }
202 |
203 | return null
204 | }
205 | }
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/FileHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dg.helpers
2 |
3 | import java.io.File
4 | import java.io.FileInputStream
5 | import java.io.FileNotFoundException
6 | import java.io.FileOutputStream
7 | import java.io.IOException
8 | import java.nio.channels.FileChannel
9 | import kotlin.math.max
10 |
11 | /**
12 | * Utilities for handling Files
13 | */
14 | @Suppress("unused")
15 | object FileHelper
16 | {
17 | /**
18 | * Move a file, even across volumes.
19 | * Does not throw on failure.
20 | * @param from origin path
21 | * @param to destination path
22 | * @param overwrite should we overwrite existing files?
23 | * @return true if successful
24 | */
25 | @JvmOverloads
26 | fun move(from: File, to: File, overwrite: Boolean = true): Boolean
27 | {
28 | try
29 | {
30 | moveWithExceptions(from, to, overwrite)
31 | return true
32 | }
33 | catch (ignore: IOException)
34 | {
35 |
36 | }
37 |
38 | return false
39 | }
40 |
41 | /**
42 | * Move a file, even across volumes.
43 | * @param from origin path
44 | * @param to destination path
45 | * @param overwrite should we overwrite existing files?
46 | * @throws IOException
47 | */
48 | @Throws(IOException::class)
49 | @JvmOverloads
50 | fun moveWithExceptions(from: File, to: File, overwrite: Boolean = true)
51 | {
52 | if (!from.exists())
53 | {
54 | throw FileNotFoundException(String.format("'from' file was not found (%s).", from.toString()))
55 | }
56 |
57 | if (overwrite && to.exists())
58 | {
59 | if (!to.delete())
60 | {
61 | throw IOException(String.format("'to' file was cannot be overwritten (%s).", to.toString()))
62 | }
63 | }
64 | if (from.renameTo(to))
65 | {
66 | return
67 | }
68 | if (to.createNewFile())
69 | {
70 | var source: FileChannel? = null
71 | var destination: FileChannel? = null
72 | try
73 | {
74 | source = FileInputStream(from).channel
75 | destination = FileOutputStream(to).channel
76 | destination!!.transferFrom(source, 0, source!!.size())
77 | }
78 | catch (ex: IOException)
79 | {
80 |
81 | to.delete()
82 | throw ex
83 | }
84 | finally
85 | {
86 | source?.close()
87 | destination?.close()
88 |
89 | from.delete()
90 | }
91 | }
92 | else
93 | {
94 | throw IOException(String.format("'to' file was cannot be created (%s).", to.toString()))
95 | }
96 | }
97 |
98 | /**
99 | * Copy a file, even across volumes.
100 | * Does not throw on failure.
101 | * @param from origin path
102 | * @param to destination path
103 | * @param overwrite should we overwrite existing files?
104 | * @return true if successful
105 | */
106 | @JvmOverloads
107 | fun copy(from: File, to: File, overwrite: Boolean = true): Boolean
108 | {
109 | try
110 | {
111 | copyWithExceptions(from, to, overwrite)
112 | return true
113 | }
114 | catch (ignore: IOException)
115 | {
116 |
117 | }
118 |
119 | return false
120 | }
121 |
122 | /**
123 | * Copy a file, even across volumes.
124 | * @param from origin path
125 | * @param to destination path
126 | * @param overwrite should we overwrite existing files?
127 | * @throws IOException
128 | */
129 | @Throws(IOException::class)
130 | @JvmOverloads
131 | fun copyWithExceptions(from: File, to: File, overwrite: Boolean = true)
132 | {
133 | if (!from.exists())
134 | {
135 | throw FileNotFoundException(String.format("'from' file was not found (%s).", from.toString()))
136 | }
137 |
138 | if (overwrite && to.exists())
139 | {
140 | if (!to.delete())
141 | {
142 | throw IOException(String.format("'to' file was cannot be overwritten (%s).", to.toString()))
143 | }
144 | }
145 | if (to.createNewFile())
146 | {
147 | var source: FileChannel? = null
148 | var destination: FileChannel? = null
149 | try
150 | {
151 | source = FileInputStream(from).channel
152 | destination = FileOutputStream(to).channel
153 | destination!!.transferFrom(source, 0, source!!.size())
154 | }
155 | catch (ex: IOException)
156 | {
157 |
158 | to.delete()
159 | throw ex
160 | }
161 | finally
162 | {
163 | source?.close()
164 | destination?.close()
165 | }
166 | }
167 | else
168 | {
169 | throw IOException(String.format("'to' file was cannot be created (%s).", to.toString()))
170 | }
171 | }
172 |
173 | /**
174 | * Return the extension of the path, from the last '.' to end of string in the last portion of the path.
175 | * If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string.
176 | * @param filePath File path to retrieve extension from
177 | * @return i.e. ".jpg", ".gz", ""
178 | */
179 | fun getExtension(filePath: String): String
180 | {
181 | val i = filePath.lastIndexOf('.')
182 | val p = max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'))
183 |
184 | return if (i > p)
185 | {
186 | filePath.substring(i)
187 | }
188 | else ""
189 |
190 | }
191 |
192 | /**
193 | * Return the extension of the path, from the last '.' to end of string in the last portion of the path.
194 | * If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string.
195 | * @param file File path to retrieve extension from
196 | * @return i.e. ".jpg", ".gz", ""
197 | */
198 | fun getExtension(file: File): String
199 | {
200 | return getExtension(file.name)
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/Helpers/src/main/java/com/dg/helpers/BitmapHelper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.dg.helpers
18 |
19 | import android.app.Activity
20 | import android.content.pm.ActivityInfo
21 | import android.graphics.Bitmap
22 | import android.graphics.BitmapFactory
23 | import android.graphics.Matrix
24 | import android.graphics.Point
25 | import android.os.Build
26 | import android.util.DisplayMetrics
27 | import android.util.Log
28 | import android.view.Surface
29 | import androidx.exifinterface.media.ExifInterface
30 |
31 | import java.io.IOException
32 | import java.io.OutputStream
33 | import java.util.Locale
34 |
35 | @Suppress("unused")
36 | object BitmapHelper
37 | {
38 |
39 | fun getScaledBitmapFromBitmap(bmp: Bitmap,
40 | maxWidth: Int,
41 | maxHeight: Int,
42 | keepAspectRatio: Boolean,
43 | freeOldBitmap: Boolean): Bitmap
44 | {
45 | @Suppress("NAME_SHADOWING")
46 | var maxWidth = maxWidth
47 | @Suppress("NAME_SHADOWING")
48 | var maxHeight = maxHeight
49 |
50 | if (keepAspectRatio)
51 | {
52 | val ratio = if (bmp.width == 0) 1f else bmp.width / bmp.height.toFloat()
53 | val newRatio = if (maxHeight == 0) 1f else maxWidth / maxHeight.toFloat()
54 |
55 | if (newRatio > ratio)
56 | {
57 | maxWidth = (maxHeight.toFloat() * ratio).toInt()
58 | }
59 | else if (newRatio < ratio)
60 | {
61 | maxHeight = (maxWidth.toFloat() / ratio).toInt()
62 | }
63 | }
64 |
65 | val scaled = Bitmap.createScaledBitmap(bmp, maxWidth, maxHeight, true)
66 |
67 | if (freeOldBitmap && scaled != bmp)
68 | {
69 | bmp.recycle()
70 | }
71 |
72 | return scaled
73 | }
74 |
75 | @Suppress("MemberVisibilityCanBePrivate")
76 | fun calculateInSampleSize(options: BitmapFactory.Options, minWidth: Int, minHeight: Int): Int
77 | {
78 | // Raw height and width of image
79 | val height = options.outHeight
80 | val width = options.outWidth
81 |
82 | var inSampleSize = 1
83 |
84 | if (minHeight in 1 until height || minWidth in 1 until width)
85 | {
86 | val halfHeight = height / 2
87 | val halfWidth = width / 2
88 |
89 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both
90 | // height and width larger than the requested height and width.
91 | while ((minHeight <= 0 || halfHeight / inSampleSize > minHeight) && (minWidth <= 0 || halfWidth / inSampleSize > minWidth))
92 | {
93 | inSampleSize *= 2
94 | }
95 | }
96 |
97 | return inSampleSize
98 | }
99 |
100 | fun writeScaledImageFromFileToStream(filePath: String,
101 | sizeConstraint: Point,
102 | compressFormat: Bitmap.CompressFormat,
103 | compressQuality: Int,
104 | outputStream: OutputStream): Boolean
105 | {
106 | var image: Bitmap? = null
107 | var success = false
108 | try
109 | {
110 | val options = BitmapFactory.Options()
111 | options.inJustDecodeBounds = true
112 | BitmapFactory.decodeFile(filePath, options)
113 | options.inSampleSize = calculateInSampleSize(options, sizeConstraint.x, sizeConstraint.y)
114 | options.inJustDecodeBounds = false
115 | image = BitmapFactory.decodeFile(filePath, options)
116 |
117 | // Try to fix rotation, as the file looses the EXIF data when re-compressed
118 | try
119 | {
120 | image = getBitmapByFixingRotationForFile(filePath, image, null, true)
121 | }
122 | catch (ignored: OutOfMemoryError)
123 | {
124 | }
125 | catch (ignored: Exception)
126 | {
127 | }
128 |
129 | // Compress directly to the output stream
130 | image!!.compress(compressFormat, compressQuality, outputStream)
131 |
132 | // We're Ok
133 | success = true
134 | }
135 | catch (ignored: OutOfMemoryError)
136 | {
137 | }
138 | catch (ignored: Exception)
139 | {
140 | }
141 |
142 | image?.recycle()
143 |
144 | return success
145 | }
146 |
147 | fun getImageSize(filePath: String): Point
148 | {
149 | try
150 | {
151 | val options = BitmapFactory.Options()
152 | options.inJustDecodeBounds = true
153 | BitmapFactory.decodeFile(filePath, options)
154 | return Point(options.outWidth, options.outHeight)
155 | }
156 | catch (ignored: OutOfMemoryError)
157 | {
158 | }
159 | catch (ignored: Exception)
160 | {
161 | }
162 |
163 | return Point(0, 0)
164 | }
165 |
166 | fun fixBitmapRotationExif(filePath: String, activityForScreenOrientation: Activity?)
167 | {
168 | try
169 | {
170 | val exif = ExifInterface(filePath)
171 |
172 | val exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
173 |
174 | if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED && Build.MANUFACTURER.toLowerCase(Locale.ENGLISH).contains("htc"))
175 | return
176 |
177 | var flippedHorizontally = false
178 | var flippedVertically = false
179 |
180 | var angle = 0
181 |
182 | when (exifOrientation)
183 | {
184 | ExifInterface.ORIENTATION_ROTATE_90 -> angle += 90
185 | ExifInterface.ORIENTATION_ROTATE_180 -> angle += 180
186 | ExifInterface.ORIENTATION_ROTATE_270 -> angle += 270
187 | ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> flippedHorizontally = true
188 | ExifInterface.ORIENTATION_FLIP_VERTICAL -> flippedVertically = true
189 | ExifInterface.ORIENTATION_TRANSPOSE ->
190 | {
191 | angle += 90
192 | flippedVertically = true
193 | }
194 | ExifInterface.ORIENTATION_TRANSVERSE ->
195 | {
196 | angle -= 90
197 | flippedVertically = true
198 | }
199 | }
200 |
201 | var orientation: Int
202 |
203 | if (activityForScreenOrientation != null)
204 | {
205 | orientation = getScreenOrientation(activityForScreenOrientation)
206 | when (orientation)
207 | {
208 | ActivityInfo.SCREEN_ORIENTATION_PORTRAIT -> angle += 90
209 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE -> angle += 180
210 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT -> angle += 270
211 | }
212 | }
213 |
214 | orientation = 0
215 | angle %= 360
216 |
217 | if (angle == -90 && flippedVertically && !flippedHorizontally)
218 | {
219 | orientation = ExifInterface.ORIENTATION_TRANSVERSE
220 | }
221 | else if (angle == -270 && flippedVertically && !flippedHorizontally)
222 | {
223 | orientation = ExifInterface.ORIENTATION_TRANSPOSE
224 | }
225 | else if (angle == -90 && !flippedVertically && flippedHorizontally)
226 | {
227 | orientation = ExifInterface.ORIENTATION_TRANSPOSE
228 | }
229 | else if (angle == -270 && !flippedVertically && flippedHorizontally)
230 | {
231 | orientation = ExifInterface.ORIENTATION_TRANSVERSE
232 | }
233 | else
234 | {
235 | while (angle < 0)
236 | {
237 | angle += 360
238 | }
239 | when (angle)
240 | {
241 | 0 -> if (flippedHorizontally)
242 | {
243 | orientation = ExifInterface.ORIENTATION_FLIP_HORIZONTAL
244 | }
245 | else if (flippedVertically)
246 | {
247 | orientation = ExifInterface.ORIENTATION_FLIP_VERTICAL
248 | }
249 | 90 -> orientation = ExifInterface.ORIENTATION_ROTATE_90
250 | 180 -> orientation = ExifInterface.ORIENTATION_ROTATE_180
251 | 270 -> orientation = ExifInterface.ORIENTATION_ROTATE_270
252 | }
253 | }
254 |
255 | if (orientation != exifOrientation)
256 | {
257 | exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation.toString())
258 | exif.saveAttributes()
259 | }
260 | }
261 | catch (e: IOException)
262 | {
263 | Log.w("TAG", "-- Error in setting image")
264 | }
265 |
266 | }
267 |
268 | @Suppress("MemberVisibilityCanBePrivate")
269 | fun getBitmapByFixingRotationForFile(filePath: String,
270 | sourceBitmap: Bitmap?,
271 | activityForScreenOrientation: Activity?,
272 | freeSourceBitmap: Boolean): Bitmap?
273 | {
274 | try
275 | {
276 | val exif = ExifInterface(filePath)
277 | var orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
278 |
279 | if (orientation == ExifInterface.ORIENTATION_UNDEFINED && Build.MANUFACTURER.toLowerCase(Locale.ENGLISH).contains("htc"))
280 | return null
281 |
282 | var flippedHorizontally = false
283 | var flippedVertically = false
284 |
285 | var angle = 0
286 |
287 | when (orientation)
288 | {
289 | ExifInterface.ORIENTATION_ROTATE_90 -> angle += 90
290 | ExifInterface.ORIENTATION_ROTATE_180 -> angle += 180
291 | ExifInterface.ORIENTATION_ROTATE_270 -> angle += 270
292 | ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> flippedHorizontally = true
293 | ExifInterface.ORIENTATION_FLIP_VERTICAL -> flippedVertically = true
294 | ExifInterface.ORIENTATION_TRANSPOSE ->
295 | {
296 | angle += 90
297 | flippedVertically = true
298 | }
299 | ExifInterface.ORIENTATION_TRANSVERSE ->
300 | {
301 | angle -= 90
302 | flippedVertically = true
303 | }
304 | }
305 |
306 | if (activityForScreenOrientation != null)
307 | {
308 | orientation = getScreenOrientation(activityForScreenOrientation)
309 | when (orientation)
310 | {
311 | ActivityInfo.SCREEN_ORIENTATION_PORTRAIT -> angle += 90
312 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE -> angle += 180
313 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT -> angle += 270
314 | }
315 | }
316 |
317 | var bmp = sourceBitmap
318 | if (bmp == null)
319 | {
320 | bmp = BitmapFactory.decodeFile(filePath, null)
321 | }
322 | if (angle != 0)
323 | {
324 | val mat = Matrix()
325 | mat.postRotate(angle.toFloat())
326 |
327 | if (flippedHorizontally)
328 | {
329 | mat.postScale(-1f, 1f)
330 | }
331 | if (flippedVertically)
332 | {
333 | mat.postScale(1f, -1f)
334 | }
335 |
336 | val rotated = Bitmap.createBitmap(bmp!!, 0, 0, bmp.width, bmp.height, mat, true)
337 | if (freeSourceBitmap || bmp != sourceBitmap)
338 | {
339 | bmp.recycle()
340 | }
341 | bmp = rotated
342 | }
343 |
344 | return bmp
345 |
346 | }
347 | catch (e: IOException)
348 | {
349 | Log.w("TAG", "-- Error in setting image")
350 | }
351 | catch (oom: OutOfMemoryError)
352 | {
353 | Log.w("TAG", "-- OOM Error in setting image")
354 | }
355 |
356 | return null
357 | }
358 |
359 | @Suppress("MemberVisibilityCanBePrivate")
360 | fun getScreenOrientation(activity: Activity): Int
361 | {
362 | val rotation = activity.windowManager.defaultDisplay.rotation
363 | val dm = DisplayMetrics()
364 | activity.windowManager.defaultDisplay.getMetrics(dm)
365 | val width = dm.widthPixels
366 | val height = dm.heightPixels
367 | val orientation: Int
368 | // if the device's natural orientation is portrait:
369 | if ((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) && height > width || (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) && width > height)
370 | {
371 | orientation = when (rotation)
372 | {
373 | Surface.ROTATION_0 -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
374 | Surface.ROTATION_90 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
375 | Surface.ROTATION_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
376 | Surface.ROTATION_270 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
377 | else ->
378 | {
379 | Log.e("getScreenOrientation", "Unknown screen orientation. Defaulting to portrait.")
380 | ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
381 | }
382 | }
383 | }
384 | else
385 | {
386 | // if the device's natural orientation is landscape or if the device
387 | // is square:
388 |
389 | orientation = when (rotation)
390 | {
391 | Surface.ROTATION_0 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
392 | Surface.ROTATION_90 -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
393 | Surface.ROTATION_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
394 | Surface.ROTATION_270 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
395 | else ->
396 | {
397 | Log.e("getScreenOrientation", "Unknown screen orientation. Defaulting to landscape.")
398 | ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
399 | }
400 | }
401 | }
402 |
403 | return orientation
404 | }
405 | }
--------------------------------------------------------------------------------