├── 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 | } --------------------------------------------------------------------------------