From 510c79426f9cdf81eca885f926ce286ab88110f5 Mon Sep 17 00:00:00 2001 From: Deepak Sharma Date: Tue, 21 Jan 2020 07:10:29 +0530 Subject: [PATCH] first commit --- .gitignore | 14 + .idea/codeStyles/Project.xml | 122 +++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/gradle.xml | 23 + .idea/misc.xml | 9 + .idea/runConfigurations.xml | 12 + .idea/vcs.xml | 6 + app/.gitignore | 1 + app/build.gradle | 55 +++ app/proguard-rules.pro | 21 + .../techcleanarch/ExampleInstrumentedTest.kt | 24 + app/src/main/AndroidManifest.xml | 46 ++ .../techcleanarch/AppApplication.kt | 51 ++ .../global/annotationdef/MediaPickerType.kt | 17 + .../global/common/MediaPickerHelper.kt | 151 ++++++ .../global/common/NetworkChangeReceiver.kt | 46 ++ .../global/customview/CircleImageView.java | 454 +++++++++++++++++ .../techcleanarch/global/koin/AppModule.kt | 71 +++ .../techcleanarch/global/koin/CommonModule.kt | 17 + .../techcleanarch/global/koin/DbModule.kt | 25 + .../techcleanarch/global/koin/RepoModule.kt | 11 + .../global/koin/ViewModelModule.kt | 12 + .../techcleanarch/global/misc/AppConstant.kt | 14 + .../global/misc/CompressImage.kt | 199 ++++++++ .../techcleanarch/global/misc/DialogUtils.kt | 43 ++ .../techcleanarch/global/misc/FileUtils.kt | 465 ++++++++++++++++++ .../global/misc/GlobalUtilities.kt | 445 +++++++++++++++++ .../techcleanarch/global/misc/Lg.kt | 203 ++++++++ .../global/misc/MediaPickerUtils.kt | 154 ++++++ .../global/misc/PermissionHelper.kt | 246 +++++++++ .../global/misc/ValidationHelper.kt | 23 + .../global/misc/ViewExtension.kt | 139 ++++++ .../techcleanarch/view/adapter/NewsAdapter.kt | 39 ++ .../techcleanarch/view/base/BaseActivity.kt | 185 +++++++ .../techcleanarch/view/base/BaseAdapter.kt | 82 +++ .../techcleanarch/view/base/BaseDialog.kt | 47 ++ .../techcleanarch/view/base/BaseFragment.kt | 135 +++++ .../techcleanarch/view/base/ScrollListener.kt | 114 +++++ .../techcleanarch/view/dialog/LoaderDialog.kt | 35 ++ .../techcleanarch/view/home/HomeActivity.kt | 50 ++ .../techcleanarch/view/home/NewsFrm.kt | 125 +++++ .../techcleanarch/view/home/ProfileFrm.kt | 71 +++ .../view/interfaces/AlertDialogListener.kt | 8 + .../interfaces/AlertRetryDialogListener.kt | 9 + .../view/splash/SplashActivity.kt | 43 ++ .../techcleanarch/viewmodel/BaseViewModel.kt | 15 + .../techcleanarch/viewmodel/NewsViewModel.kt | 22 + app/src/main/res/anim/fade_in.xml | 11 + app/src/main/res/anim/fade_out.xml | 11 + app/src/main/res/anim/trans_left_in.xml | 7 + app/src/main/res/anim/trans_left_out.xml | 7 + app/src/main/res/anim/trans_right_in.xml | 9 + app/src/main/res/anim/trans_right_out.xml | 8 + .../drawable-v24/ic_launcher_foreground.xml | 34 ++ app/src/main/res/drawable/circle_white.xml | 9 + app/src/main/res/drawable/ic_back.xml | 9 + .../res/drawable/ic_launcher_background.xml | 170 +++++++ app/src/main/res/drawable/ic_profile.xml | 9 + app/src/main/res/drawable/logo.png | Bin 0 -> 10915 bytes app/src/main/res/layout/activity_common.xml | 33 ++ app/src/main/res/layout/activity_splash.xml | 41 ++ app/src/main/res/layout/dialog_loader.xml | 49 ++ app/src/main/res/layout/frm_news.xml | 44 ++ app/src/main/res/layout/frm_profile.xml | 48 ++ app/src/main/res/layout/row_channel_list.xml | 62 +++ app/src/main/res/layout/toolbar.xml | 77 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/raw/loading.json | 1 + app/src/main/res/raw/sleepy_loading.json | 1 + app/src/main/res/values/attrs.xml | 30 ++ app/src/main/res/values/colors.xml | 15 + app/src/main/res/values/strings.xml | 24 + app/src/main/res/values/styles.xml | 29 ++ app/src/main/res/xml/file_provider_paths.xml | 18 + .../techcleanarch/ExampleUnitTest.kt | 17 + build.gradle | 47 ++ data/.gitignore | 1 + data/build.gradle | 33 ++ data/consumer-rules.pro | 0 data/proguard-rules.pro | 21 + .../data/ExampleInstrumentedTest.kt | 24 + data/src/main/AndroidManifest.xml | 2 + .../data/common/NotificationData.kt | 16 + .../webaddicted/data/repo/BaseRepository.kt | 15 + .../webaddicted/data/repo/NewsRepository.kt | 25 + .../data/sharedpref/PreferenceConstant.kt | 17 + .../data/sharedpref/PreferenceMgr.kt | 60 +++ .../data/sharedpref/PreferenceUtils.kt | 208 ++++++++ data/src/main/res/values/strings.xml | 3 + .../com/webaddicted/data/ExampleUnitTest.kt | 17 + database/.gitignore | 1 + database/build.gradle | 40 ++ database/consumer-rules.pro | 0 database/proguard-rules.pro | 21 + .../database/ExampleInstrumentedTest.kt | 24 + database/src/main/AndroidManifest.xml | 2 + .../webaddicted/database/dao/UserInfoDao.kt | 25 + .../database/database/AppDatabase.kt | 15 + .../database/entity/UserInfoEntity.kt | 18 + .../database/migration/DbMigrationHelper.kt | 25 + .../webaddicted/database/utils/DbConstant.kt | 11 + database/src/main/res/values/strings.xml | 3 + .../webaddicted/database/ExampleUnitTest.kt | 17 + gradle.properties | 21 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++++ gradlew.bat | 84 ++++ model/.gitignore | 1 + model/build.gradle | 36 ++ model/consumer-rules.pro | 0 model/proguard-rules.pro | 21 + .../model/ExampleInstrumentedTest.kt | 24 + model/src/main/AndroidManifest.xml | 2 + .../webaddicted/model/news/NewsChanelRespo.kt | 30 ++ .../model/sharedpref/PreferenceBean.kt | 15 + model/src/main/res/values/strings.xml | 3 + .../com/webaddicted/model/ExampleUnitTest.kt | 17 + network/.gitignore | 1 + network/build.gradle | 38 ++ network/consumer-rules.pro | 0 network/proguard-rules.pro | 21 + .../network/ExampleInstrumentedTest.kt | 24 + network/src/main/AndroidManifest.xml | 6 + .../network/apiutils/ApiConstant.kt | 13 + .../network/apiutils/ApiResponse.kt | 64 +++ .../network/apiutils/ApiServices.kt | 14 + .../network/apiutils/DataFetchCall.kt | 41 ++ .../network/apiutils/ReflectionUtil.kt | 20 + network/src/main/res/values/strings.xml | 3 + .../webaddicted/network/ExampleUnitTest.kt | 17 + settings.gradle | 2 + 143 files changed, 6274 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/webaddicted/techcleanarch/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/AppApplication.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/annotationdef/MediaPickerType.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/common/MediaPickerHelper.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/common/NetworkChangeReceiver.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/customview/CircleImageView.java create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/koin/AppModule.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/koin/CommonModule.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/koin/DbModule.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/koin/RepoModule.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/koin/ViewModelModule.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/AppConstant.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/CompressImage.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/DialogUtils.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/FileUtils.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/GlobalUtilities.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/Lg.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/MediaPickerUtils.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/PermissionHelper.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/ValidationHelper.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/global/misc/ViewExtension.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/adapter/NewsAdapter.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseActivity.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseAdapter.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseDialog.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseFragment.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/base/ScrollListener.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/dialog/LoaderDialog.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/home/HomeActivity.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/home/NewsFrm.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/home/ProfileFrm.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertDialogListener.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertRetryDialogListener.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/view/splash/SplashActivity.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/viewmodel/BaseViewModel.kt create mode 100644 app/src/main/java/com/webaddicted/techcleanarch/viewmodel/NewsViewModel.kt create mode 100644 app/src/main/res/anim/fade_in.xml create mode 100644 app/src/main/res/anim/fade_out.xml create mode 100644 app/src/main/res/anim/trans_left_in.xml create mode 100644 app/src/main/res/anim/trans_left_out.xml create mode 100644 app/src/main/res/anim/trans_right_in.xml create mode 100644 app/src/main/res/anim/trans_right_out.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/circle_white.xml create mode 100644 app/src/main/res/drawable/ic_back.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_profile.xml create mode 100644 app/src/main/res/drawable/logo.png create mode 100644 app/src/main/res/layout/activity_common.xml create mode 100644 app/src/main/res/layout/activity_splash.xml create mode 100644 app/src/main/res/layout/dialog_loader.xml create mode 100644 app/src/main/res/layout/frm_news.xml create mode 100644 app/src/main/res/layout/frm_profile.xml create mode 100644 app/src/main/res/layout/row_channel_list.xml create mode 100644 app/src/main/res/layout/toolbar.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/raw/loading.json create mode 100644 app/src/main/res/raw/sleepy_loading.json create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/file_provider_paths.xml create mode 100644 app/src/test/java/com/webaddicted/techcleanarch/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 data/.gitignore create mode 100644 data/build.gradle create mode 100644 data/consumer-rules.pro create mode 100644 data/proguard-rules.pro create mode 100644 data/src/androidTest/java/com/webaddicted/data/ExampleInstrumentedTest.kt create mode 100644 data/src/main/AndroidManifest.xml create mode 100644 data/src/main/java/com/webaddicted/data/common/NotificationData.kt create mode 100644 data/src/main/java/com/webaddicted/data/repo/BaseRepository.kt create mode 100644 data/src/main/java/com/webaddicted/data/repo/NewsRepository.kt create mode 100644 data/src/main/java/com/webaddicted/data/sharedpref/PreferenceConstant.kt create mode 100644 data/src/main/java/com/webaddicted/data/sharedpref/PreferenceMgr.kt create mode 100644 data/src/main/java/com/webaddicted/data/sharedpref/PreferenceUtils.kt create mode 100644 data/src/main/res/values/strings.xml create mode 100644 data/src/test/java/com/webaddicted/data/ExampleUnitTest.kt create mode 100644 database/.gitignore create mode 100644 database/build.gradle create mode 100644 database/consumer-rules.pro create mode 100644 database/proguard-rules.pro create mode 100644 database/src/androidTest/java/com/webaddicted/database/ExampleInstrumentedTest.kt create mode 100644 database/src/main/AndroidManifest.xml create mode 100644 database/src/main/java/com/webaddicted/database/dao/UserInfoDao.kt create mode 100644 database/src/main/java/com/webaddicted/database/database/AppDatabase.kt create mode 100644 database/src/main/java/com/webaddicted/database/entity/UserInfoEntity.kt create mode 100644 database/src/main/java/com/webaddicted/database/migration/DbMigrationHelper.kt create mode 100644 database/src/main/java/com/webaddicted/database/utils/DbConstant.kt create mode 100644 database/src/main/res/values/strings.xml create mode 100644 database/src/test/java/com/webaddicted/database/ExampleUnitTest.kt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 model/.gitignore create mode 100644 model/build.gradle create mode 100644 model/consumer-rules.pro create mode 100644 model/proguard-rules.pro create mode 100644 model/src/androidTest/java/com/webaddicted/model/ExampleInstrumentedTest.kt create mode 100644 model/src/main/AndroidManifest.xml create mode 100644 model/src/main/java/com/webaddicted/model/news/NewsChanelRespo.kt create mode 100644 model/src/main/java/com/webaddicted/model/sharedpref/PreferenceBean.kt create mode 100644 model/src/main/res/values/strings.xml create mode 100644 model/src/test/java/com/webaddicted/model/ExampleUnitTest.kt create mode 100644 network/.gitignore create mode 100644 network/build.gradle create mode 100644 network/consumer-rules.pro create mode 100644 network/proguard-rules.pro create mode 100644 network/src/androidTest/java/com/webaddicted/network/ExampleInstrumentedTest.kt create mode 100644 network/src/main/AndroidManifest.xml create mode 100644 network/src/main/java/com/webaddicted/network/apiutils/ApiConstant.kt create mode 100644 network/src/main/java/com/webaddicted/network/apiutils/ApiResponse.kt create mode 100644 network/src/main/java/com/webaddicted/network/apiutils/ApiServices.kt create mode 100644 network/src/main/java/com/webaddicted/network/apiutils/DataFetchCall.kt create mode 100644 network/src/main/java/com/webaddicted/network/apiutils/ReflectionUtil.kt create mode 100644 network/src/main/res/values/strings.xml create mode 100644 network/src/test/java/com/webaddicted/network/ExampleUnitTest.kt create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..88ea3aa --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,122 @@ + + + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..3c266c3 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7bfef59 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f0b5de2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,55 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.webaddicted.techcleanarch" + minSdkVersion 16 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + dataBinding { + enabled = true + } + android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + } +} + +dependencies { + implementation 'com.android.support:design:29.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' +// livedata + implementation "android.arch.lifecycle:extensions:$LIVEDATA_VERSION" + annotationProcessor "android.arch.lifecycle:compiler:$LIVEDATA_VERSION" + annotationProcessor "com.android.databinding:compiler:3.1.2" + implementation "android.arch.lifecycle:runtime:$LIVEDATA_VERSION" + kapt "android.arch.lifecycle:compiler:1.1.0" + api project(path: ':data') + +// Recycler view + implementation "androidx.recyclerview:recyclerview:$RECYCLER_VIEW_VERSION" + implementation "androidx.cardview:cardview:$CARD_VERSION" + implementation "com.github.bumptech.glide:glide:$GLIDE_VERSION" +// lottie animation + implementation 'com.airbnb.android:lottie:3.0.0' +// dimension + implementation 'com.intuit.sdp:sdp-android:1.0.6' +} +kapt { + generateStubs = true +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/webaddicted/techcleanarch/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/webaddicted/techcleanarch/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..cef95fc --- /dev/null +++ b/app/src/androidTest/java/com/webaddicted/techcleanarch/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.webaddicted.techcleanarch + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.techcleanarch", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2a4d528 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/AppApplication.kt b/app/src/main/java/com/webaddicted/techcleanarch/AppApplication.kt new file mode 100644 index 0000000..cd39417 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/AppApplication.kt @@ -0,0 +1,51 @@ +package com.webaddicted.techcleanarch + +import android.app.Application +import android.content.Context +import android.content.IntentFilter +import android.net.ConnectivityManager +import com.webaddicted.data.sharedPref.PreferenceUtils +import com.webaddicted.techcleanarch.global.common.NetworkChangeReceiver +import com.webaddicted.techcleanarch.global.koin.* +import com.webaddicted.techcleanarch.global.misc.FileUtils +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin +import org.koin.core.module.Module + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class AppApplication : Application() { + private val mNetworkReceiver = NetworkChangeReceiver() + companion object { + lateinit var context: Context + } + + override fun onCreate() { + super.onCreate() + context = this + FileUtils.createApplicationFolder() + PreferenceUtils.getInstance(this) + startKoin { + androidLogger() + androidContext(this@AppApplication) + modules(getModule()) + } + checkInternetConnection() + } + + + private fun getModule(): Iterable { + return listOf( + appModule, + viewModelModule, + repoModule, + commonModelModule, + dbModule + ) + } + private fun checkInternetConnection() { + registerReceiver(mNetworkReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/annotationdef/MediaPickerType.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/annotationdef/MediaPickerType.kt new file mode 100644 index 0000000..d599c9d --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/annotationdef/MediaPickerType.kt @@ -0,0 +1,17 @@ +package com.webaddicted.techcleanarch.global.annotationdef + +import androidx.annotation.IntDef +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class MediaPickerType { + @IntDef(CAPTURE_IMAGE, SELECT_IMAGE, SCAN_DL) + @Retention(AnnotationRetention.SOURCE) + annotation class MediaType + + companion object { + const val CAPTURE_IMAGE = 500 + const val SELECT_IMAGE = 501 + const val SCAN_DL = 502 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/common/MediaPickerHelper.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/common/MediaPickerHelper.kt new file mode 100644 index 0000000..9ff2ebf --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/common/MediaPickerHelper.kt @@ -0,0 +1,151 @@ +package com.webaddicted.techcleanarch.global.common + +import android.app.Activity +import android.content.ContentUris +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.DocumentsContract +import android.provider.MediaStore +import java.io.File +import java.util.ArrayList +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class MediaPickerHelper { + fun getData(activity: Activity, data: Intent?): ArrayList { + val selectedImage = ArrayList() + if (data?.clipData != null) { + for (i in 0 until data.clipData!!.itemCount) { + val uri = data.clipData!!.getItemAt(i).uri + selectedImage.add(File(getPath(activity, uri)!!)) + } + } else { + val pathh = getPath(activity, data?.data) + selectedImage.add(File(pathh!!)) + } + return selectedImage + } + + + fun getPath(context: Context, uri: Uri?): String? { + // DocumentProvider + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri!!)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + + if ("primary".equals(type, ignoreCase = true)) { + return Environment.getExternalStorageDirectory().toString() + "/" + split[1] + } + + // TODO handle non-primary volumes + } else if (isDownloadsDocument(uri)) { + + val id = DocumentsContract.getDocumentId(uri) + val contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id) + ) + + return getDataColumn(context, contentUri, null, null) + } else if (isMediaDocument(uri)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + + var contentUri: Uri? = null + if ("image" == type) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } else if ("video" == type) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } else if ("audio" == type) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + val selection = "_id=?" + val selectionArgs = arrayOf(split[1]) + + return getDataColumn(context, contentUri, selection, selectionArgs) + }// MediaProvider + // DownloadsProvider + } else if ("content".equals(uri!!.scheme!!, ignoreCase = true)) { + return getDataColumn(context, uri, null, null) + } else if ("file".equals(uri.scheme!!, ignoreCase = true)) { + return uri.path + }// File + // MediaStore (and general) + + return null + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + fun getDataColumn( + context: Context, uri: Uri?, selection: String?, + selectionArgs: Array? + ): String? { + + var cursor: Cursor? = null + val column = "_data" + val projection = arrayOf(column) + + try { + cursor = + context.contentResolver.query(uri!!, projection, selection, selectionArgs, null) + if (cursor != null && cursor.moveToFirst()) { + val column_index = cursor.getColumnIndexOrThrow(column) + return cursor.getString(column_index) + } + } finally { + cursor?.close() + } + return null + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + fun isExternalStorageDocument(uri: Uri): Boolean { + return "com.android.externalstorage.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + fun isDownloadsDocument(uri: Uri): Boolean { + return "com.android.providers.downloads.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + fun isMediaDocument(uri: Uri): Boolean { + return "com.android.providers.media.documents" == uri.authority + } + + private fun updateGallery(context: Context, imagePath: String) { + val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) + val f = File(imagePath) + val contentUri = Uri.fromFile(f) + mediaScanIntent.data = contentUri + context.sendBroadcast(mediaScanIntent) + } + +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/common/NetworkChangeReceiver.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/common/NetworkChangeReceiver.kt new file mode 100644 index 0000000..2b51621 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/common/NetworkChangeReceiver.kt @@ -0,0 +1,46 @@ +package com.webaddicted.techcleanarch.global.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.webaddicted.techcleanarch.global.misc.isNetworkAvailable + +/** + * The type Network change receiver. + * Instantiates a new Network change receiver. + */ +class NetworkChangeReceiver : BroadcastReceiver() { + companion object { + private val TAG = NetworkChangeReceiver::class.java.simpleName + /** + * The constant connectivityReceiverListener. + */ + var connectivityReceiverListener: ConnectivityReceiverListener? = null + fun isInternetAvailable(connectivityReceiverListener: ConnectivityReceiverListener?){ + this.connectivityReceiverListener = connectivityReceiverListener; + } + + } + override fun onReceive(context: Context, intent: Intent) { + if (context?.isNetworkAvailable()) { + if (connectivityReceiverListener != null) + connectivityReceiverListener!!.onNetworkConnectionChanged(true) + } else { + if (connectivityReceiverListener != null) + connectivityReceiverListener!!.onNetworkConnectionChanged(false) + } + } + + + /** + * The interface Connectivity receiver listener. + */ + interface ConnectivityReceiverListener { + /** + * This method is invoked bu receiver when internet connection enables or disables. + * + * @param isConnected network connectivity status. + */ + fun onNetworkConnectionChanged(isConnected: Boolean) + } +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/customview/CircleImageView.java b/app/src/main/java/com/webaddicted/techcleanarch/global/customview/CircleImageView.java new file mode 100644 index 0000000..9511522 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/customview/CircleImageView.java @@ -0,0 +1,454 @@ +package com.webaddicted.techcleanarch.global.customview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.RequiresApi; +import androidx.appcompat.widget.AppCompatImageView; + +import com.webaddicted.techcleanarch.R; + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +public class CircleImageView extends AppCompatImageView { + + private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; + + private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; + private static final int COLORDRAWABLE_DIMENSION = 2; + + private final RectF mDrawableRect = new RectF(); + private final RectF mBorderRect = new RectF(); + + private final Matrix mShaderMatrix = new Matrix(); + private final Paint mBitmapPaint = new Paint(); + private final Paint mBorderPaint = new Paint(); + private final Paint mCircleBackgroundPaint = new Paint(); + private static final int DEFAULT_BORDER_WIDTH = 0; + private static final int DEFAULT_BORDER_COLOR = Color.BLACK; + private static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT; + private static final boolean DEFAULT_BORDER_OVERLAY = false; + + + private int mBorderColor = DEFAULT_BORDER_COLOR; + private int mBorderWidth = DEFAULT_BORDER_WIDTH; + private int mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR; + + private Bitmap mBitmap; + private BitmapShader mBitmapShader; + private int mBitmapWidth; + private int mBitmapHeight; + + private float mDrawableRadius; + private float mBorderRadius; + + private ColorFilter mColorFilter; + + private boolean mReady; + private boolean mSetupPending; + private boolean mBorderOverlay; + private boolean mDisableCircularTransformation; + + public CircleImageView(Context context) { + super(context); + + init(); + } + + public CircleImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CircleImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); + + mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH); + mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR); + mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY); + + // Look for deprecated civ_fill_color if civ_circle_background_color is not set + if (a.hasValue(R.styleable.CircleImageView_civ_circle_background_color)) { + mCircleBackgroundColor = a.getColor(R.styleable.CircleImageView_civ_circle_background_color, + DEFAULT_CIRCLE_BACKGROUND_COLOR); + } else if (a.hasValue(R.styleable.CircleImageView_civ_fill_color)) { + mCircleBackgroundColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, + DEFAULT_CIRCLE_BACKGROUND_COLOR); + } + + a.recycle(); + + init(); + } + + private void init() { + super.setScaleType(SCALE_TYPE); + mReady = true; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setOutlineProvider(new OutlineProvider()); + } + + if (mSetupPending) { + setup(); + mSetupPending = false; + } + } + + @Override + public ScaleType getScaleType() { + return SCALE_TYPE; + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDisableCircularTransformation) { + super.onDraw(canvas); + return; + } + + if (mBitmap == null) { + return; + } + + if (mCircleBackgroundColor != Color.TRANSPARENT) { + canvas.drawCircle(mDrawableRect.centerX(), + mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint); + } + canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint); + if (mBorderWidth > 0) { + canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + setup(); + } + + @Override + public void setPadding(int left, int top, int right, int bottom) { + super.setPadding(left, top, right, bottom); + setup(); + } + + @Override + public void setPaddingRelative(int start, int top, int end, int bottom) { + super.setPaddingRelative(start, top, end, bottom); + setup(); + } + + public int getBorderColor() { + return mBorderColor; + } + + public void setBorderColor(@ColorInt int borderColor) { + if (borderColor == mBorderColor) { + return; + } + + mBorderColor = borderColor; + mBorderPaint.setColor(mBorderColor); + invalidate(); + } + + /** + * @deprecated Use {@link #setBorderColor(int)} instead + */ + @Deprecated + public void setBorderColorResource(@ColorRes int borderColorRes) { + setBorderColor(getContext().getResources().getColor(borderColorRes)); + } + + public int getCircleBackgroundColor() { + return mCircleBackgroundColor; + } + + public void setCircleBackgroundColor(@ColorInt int circleBackgroundColor) { + if (circleBackgroundColor == mCircleBackgroundColor) { + return; + } + + mCircleBackgroundColor = circleBackgroundColor; + mCircleBackgroundPaint.setColor(circleBackgroundColor); + invalidate(); + } + + public void setCircleBackgroundColorResource(@ColorRes int circleBackgroundRes) { + setCircleBackgroundColor(getContext().getResources().getColor(circleBackgroundRes)); + } + + /** + * Return the color drawn behind the circle-shaped drawable. + * + * @return The color drawn behind the drawable + * + * @deprecated Use {@link #getCircleBackgroundColor()} instead. + */ + @Deprecated + public int getFillColor() { + return getCircleBackgroundColor(); + } + + /** + * Set a color to be drawn behind the circle-shaped drawable. Note that + * this has no effect if the drawable is opaque or no drawable is set. + * + * @param fillColor The color to be drawn behind the drawable + * + * @deprecated Use {@link #setCircleBackgroundColor(int)} instead. + */ + @Deprecated + public void setFillColor(@ColorInt int fillColor) { + setCircleBackgroundColor(fillColor); + } + + /** + * Set a color to be drawn behind the circle-shaped drawable. Note that + * this has no effect if the drawable is opaque or no drawable is set. + * + * @param fillColorRes The color resource to be resolved to a color and + * drawn behind the drawable + * + * @deprecated Use {@link #setCircleBackgroundColorResource(int)} instead. + */ + @Deprecated + public void setFillColorResource(@ColorRes int fillColorRes) { + setCircleBackgroundColorResource(fillColorRes); + } + + public int getBorderWidth() { + return mBorderWidth; + } + + public void setBorderWidth(int borderWidth) { + if (borderWidth == mBorderWidth) { + return; + } + + mBorderWidth = borderWidth; + setup(); + } + + public void setDisableCircularTransformation(boolean disableCircularTransformation) { + if (mDisableCircularTransformation == disableCircularTransformation) { + return; + } + + mDisableCircularTransformation = disableCircularTransformation; + initializeBitmap(); + } + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + initializeBitmap(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + initializeBitmap(); + } + + @Override + public void setImageResource(@DrawableRes int resId) { + super.setImageResource(resId); + initializeBitmap(); + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + initializeBitmap(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + if (cf == mColorFilter) { + return; + } + + mColorFilter = cf; + applyColorFilter(); + invalidate(); + } + + @Override + public ColorFilter getColorFilter() { + return mColorFilter; + } + + private void applyColorFilter() { + if (mBitmapPaint != null) { + mBitmapPaint.setColorFilter(mColorFilter); + } + } + + private Bitmap getBitmapFromDrawable(Drawable drawable) { + if (drawable == null) { + return null; + } + + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + try { + Bitmap bitmap; + + if (drawable instanceof ColorDrawable) { + bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), BITMAP_CONFIG); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void initializeBitmap() { + if (mDisableCircularTransformation) { + mBitmap = null; + } else { + mBitmap = getBitmapFromDrawable(getDrawable()); + } + setup(); + } + + private void setup() { + if (!mReady) { + mSetupPending = true; + return; + } + + if (getWidth() == 0 && getHeight() == 0) { + return; + } + + if (mBitmap == null) { + invalidate(); + return; + } + + mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + + mBitmapPaint.setAntiAlias(true); + mBitmapPaint.setShader(mBitmapShader); + + mBorderPaint.setStyle(Paint.Style.STROKE); + mBorderPaint.setAntiAlias(true); + mBorderPaint.setColor(mBorderColor); + mBorderPaint.setStrokeWidth(mBorderWidth); + + mCircleBackgroundPaint.setStyle(Paint.Style.FILL); + mCircleBackgroundPaint.setAntiAlias(true); + mCircleBackgroundPaint.setColor(mCircleBackgroundColor); + + mBitmapHeight = mBitmap.getHeight(); + mBitmapWidth = mBitmap.getWidth(); + + mBorderRect.set(calculateBounds()); + mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, + (mBorderRect.width() - mBorderWidth) / 2.0f); + + mDrawableRect.set(mBorderRect); + if (!mBorderOverlay && mBorderWidth > 0) { + mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f); + } + mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f); + + applyColorFilter(); + updateShaderMatrix(); + invalidate(); + } + + private RectF calculateBounds() { + int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight(); + int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom(); + + int sideLength = Math.min(availableWidth, availableHeight); + + float left = getPaddingLeft() + (availableWidth - sideLength) / 2f; + float top = getPaddingTop() + (availableHeight - sideLength) / 2f; + + return new RectF(left, top, left + sideLength, top + sideLength); + } + + private void updateShaderMatrix() { + float scale; + float dx = 0; + float dy = 0; + + mShaderMatrix.set(null); + + if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { + scale = mDrawableRect.height() / (float) mBitmapHeight; + dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f; + } else { + scale = mDrawableRect.width() / (float) mBitmapWidth; + dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f; + } + + mShaderMatrix.setScale(scale, scale); + mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top); + + mBitmapShader.setLocalMatrix(mShaderMatrix); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return inTouchableArea(event.getX(), event.getY()) && super.onTouchEvent(event); + } + + private boolean inTouchableArea(float x, float y) { + return Math.pow(x - mBorderRect.centerX(), 2) + Math.pow(y - mBorderRect.centerY(), 2) + <= Math.pow(mBorderRadius, 2); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private class OutlineProvider extends ViewOutlineProvider { + + @Override + public void getOutline(View view, Outline outline) { + Rect bounds = new Rect(); + mBorderRect.roundOut(bounds); + outline.setRoundRect(bounds, bounds.width() / 2.0f); + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/koin/AppModule.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/AppModule.kt new file mode 100644 index 0000000..742de54 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/AppModule.kt @@ -0,0 +1,71 @@ +package com.webaddicted.techcleanarch.global.koin + + +import androidx.room.Room +import com.google.gson.FieldNamingPolicy +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory +import com.webaddicted.database.database.AppDatabase +import com.webaddicted.database.utils.DbConstant +import com.webaddicted.network.apiutils.ApiConstant +import com.webaddicted.network.apiutils.ApiServices +import com.webaddicted.network.apiutils.ReflectionUtil +import com.webaddicted.techcleanarch.AppApplication +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.koin.android.ext.koin.androidApplication +import org.koin.dsl.module +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +val appModule = module { + + /* PROVIDE GSON SINGLETON */ + single { + val builder = + GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + builder.setLenient().create() + } + + /* PROVIDE RETROFIT SINGLETON */ + single { + var loggingInterceptor = HttpLoggingInterceptor() + loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + + var httpClient = OkHttpClient.Builder() + httpClient.addInterceptor(loggingInterceptor) + httpClient.connectTimeout(ApiConstant.API_TIME_OUT, TimeUnit.MILLISECONDS) + httpClient.addInterceptor { chain -> + val request = chain.request().newBuilder().build() + chain.proceed(request) + } + var okHttpClient = httpClient.build() + + Retrofit.Builder() + .baseUrl(ApiConstant.BASE_URL) + .addCallAdapterFactory(CoroutineCallAdapterFactory()) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(get() as Gson)) + .build() + } + + /*Provide API Service Singleton */ + single { + (get()).create(ApiServices::class.java) + } + single { + Room.databaseBuilder( + (androidApplication() as AppApplication), + AppDatabase::class.java, + DbConstant.DB_NAME + ).allowMainThreadQueries().build() + //.addMigrations(migration4To5, migration5To6).build() + } +// single { (get() as AppDatabase).userInfoDao() } + single { ReflectionUtil(get()) } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/koin/CommonModule.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/CommonModule.kt new file mode 100644 index 0000000..b0f8c1f --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/CommonModule.kt @@ -0,0 +1,17 @@ +package com.webaddicted.techcleanarch.global.koin + + +import com.webaddicted.data.sharedPref.PreferenceMgr +import com.webaddicted.data.sharedPref.PreferenceUtils +import com.webaddicted.techcleanarch.global.misc.MediaPickerUtils +import org.koin.dsl.module + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +val commonModelModule = module { + single { PreferenceUtils() } + single { PreferenceMgr(get()) } + single { MediaPickerUtils() } + +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/koin/DbModule.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/DbModule.kt new file mode 100644 index 0000000..dc25c05 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/DbModule.kt @@ -0,0 +1,25 @@ +package com.webaddicted.techcleanarch.global.koin + + +import androidx.room.Room +import com.webaddicted.database.database.AppDatabase +import com.webaddicted.database.utils.DbConstant +import com.webaddicted.techcleanarch.AppApplication +import org.koin.android.ext.koin.androidApplication +import org.koin.dsl.module + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +val dbModule = module(override = true) { + + single { + Room.databaseBuilder( + (androidApplication() as AppApplication), + AppDatabase::class.java, + DbConstant.DB_NAME + ).allowMainThreadQueries().build() + } + + single { (get() as AppDatabase).userInfoDao() } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/koin/RepoModule.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/RepoModule.kt new file mode 100644 index 0000000..f7a8553 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/RepoModule.kt @@ -0,0 +1,11 @@ +package com.webaddicted.techcleanarch.global.koin +import com.webaddicted.data.repo.NewsRepository +import org.koin.dsl.module +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +val repoModule = module { + + single { NewsRepository(get()) } + +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/koin/ViewModelModule.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/ViewModelModule.kt new file mode 100644 index 0000000..b5bbe4a --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/koin/ViewModelModule.kt @@ -0,0 +1,12 @@ +package com.webaddicted.techcleanarch.global.koin + +import com.webaddicted.techcleanarch.viewmodel.NewsViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +val viewModelModule = module { + viewModel { NewsViewModel(get()) } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/AppConstant.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/AppConstant.kt new file mode 100644 index 0000000..c4cc484 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/AppConstant.kt @@ -0,0 +1,14 @@ +package com.webaddicted.techcleanarch.global.misc + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class AppConstant { + companion object { + const val PAGINATION_SIZE: Int = 10 + const val SPLASH_DELAY: Long = 3000 + val NOTIFICATION_CHANNEL_ID = "com.example" + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/CompressImage.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/CompressImage.kt new file mode 100644 index 0000000..56677bc --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/CompressImage.kt @@ -0,0 +1,199 @@ +package com.webaddicted.kotlinproject.global.common + +import android.app.Activity +import android.content.Context +import android.graphics.* +import android.media.ExifInterface +import android.net.Uri +import android.os.Environment +import android.provider.MediaStore +import android.util.Log +import com.webaddicted.techcleanarch.global.misc.FileUtils +import java.io.File +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class CompressImage { + companion object { + private val TAG = CompressImage::class.java.simpleName + private var mContext: Context? = null + private val imagePath = Environment.getExternalStorageDirectory().toString() + "/comp/" + + private val filename: String + get() { + val file = File(Environment.getExternalStorageDirectory().path, "/FirstFood") + if (!file.exists()) { + file.mkdirs() + } + return file.absolutePath + "/" + System.currentTimeMillis() + ".jpg" + + } + + public fun compressImage(context: Activity, imageUri: String): File { + mContext = context + // String filePath = getRealPathFromURI(imageUri); + var scaledBitmap: Bitmap? = null + val options = BitmapFactory.Options() + options.inJustDecodeBounds = true + var bmp = BitmapFactory.decodeFile(imageUri, options) + var actualHeight = options.outHeight + var actualWidth = options.outWidth + val maxHeight = 816.0f + val maxWidth = 612.0f + var imgRatio = (actualWidth / actualHeight).toFloat() + val maxRatio = maxWidth / maxHeight + + if (actualHeight > maxHeight || actualWidth > maxWidth) { + if (imgRatio < maxRatio) { + imgRatio = maxHeight / actualHeight + actualWidth = (imgRatio * actualWidth).toInt() + actualHeight = maxHeight.toInt() + } else if (imgRatio > maxRatio) { + imgRatio = maxWidth / actualWidth + actualHeight = (imgRatio * actualHeight).toInt() + actualWidth = maxWidth.toInt() + } else { + actualHeight = maxHeight.toInt() + actualWidth = maxWidth.toInt() + } + } + + options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight) + options.inJustDecodeBounds = false + options.inDither = false + options.inPurgeable = true + options.inInputShareable = true + options.inTempStorage = ByteArray(16 * 1024) + + try { + bmp = BitmapFactory.decodeFile(imageUri, options) + } catch (exception: OutOfMemoryError) { + exception.printStackTrace() + + } + + try { + scaledBitmap = + Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888) + } catch (exception: OutOfMemoryError) { + exception.printStackTrace() + } + + val ratioX = actualWidth / options.outWidth.toFloat() + val ratioY = actualHeight / options.outHeight.toFloat() + val middleX = actualWidth / 2.0f + val middleY = actualHeight / 2.0f + + val scaleMatrix = Matrix() + scaleMatrix.setScale(ratioX, ratioY, middleX, middleY) + + val canvas = Canvas(scaledBitmap!!) + canvas.setMatrix(scaleMatrix) + canvas.drawBitmap( + bmp, + middleX - bmp.width / 2, + middleY - bmp.height / 2, + Paint(Paint.FILTER_BITMAP_FLAG) + ) + + val exif: ExifInterface + try { + exif = ExifInterface(imageUri) + + val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0) + Lg.d(TAG, "Exif: $orientation") + val matrix = Matrix() + if (orientation == 6) { + matrix.postRotate(90f) + Lg.d(TAG, "Exif: $orientation") + } else if (orientation == 3) { + matrix.postRotate(180f) + Lg.d(TAG, "Exif: $orientation") + } else if (orientation == 8) { + matrix.postRotate(270f) + Lg.d(TAG, "Exif: $orientation") + } + scaledBitmap = Bitmap.createBitmap( + scaledBitmap, + 0, + 0, + scaledBitmap.width, + scaledBitmap.height, + matrix, + true + ) + } catch (e: IOException) { + e.printStackTrace() + } + + return SaveImage(scaledBitmap!!) + } + + private fun getRealPathFromURI(contentURI: String): String? { + val contentUri = Uri.parse(contentURI) + val cursor = mContext!!.contentResolver.query(contentUri, null, null, null, null) + if (cursor == null) { + return contentUri.path + } else { + cursor.moveToFirst() + val idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA) + return cursor.getString(idx) + } + } + + private fun calculateInSampleSize( + options: BitmapFactory.Options, + reqWidth: Int, + reqHeight: Int + ): Int { + val height = options.outHeight + val width = options.outWidth + var inSampleSize = 1 + + if (height > reqHeight || width > reqWidth) { + val heightRatio = Math.round(height.toFloat() / reqHeight.toFloat()) + val widthRatio = Math.round(width.toFloat() / reqWidth.toFloat()) + inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio + } + val totalPixels = (width * height).toFloat() + val totalReqPixelsCap = (reqWidth * reqHeight * 2).toFloat() + + while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { + inSampleSize++ + } + + return inSampleSize + } + + private fun SaveImage(finalBitmap: Bitmap): File { + val dirFile: File + Lg.d(TAG, "SaveImage: " + finalBitmap.toString().length) + if (imagePath != null) { + // dirFile = new File(Environment.getExternalStorageDirectory() + imagePath); + dirFile = FileUtils.subFolder() + } else + dirFile = File(mContext!!.cacheDir.toString()) + if (!dirFile.exists()) { + dirFile.mkdirs() + } + val files = File(dirFile, System.currentTimeMillis().toString() + ".jpg") + var fileOutputStream: FileOutputStream? = null + try { + files.createNewFile() + fileOutputStream = FileOutputStream(files) + finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream) + fileOutputStream.flush() + fileOutputStream.close() + } catch (fileNotFoundException: FileNotFoundException) { + Log.e(TAG, "File not found!", fileNotFoundException) + } catch (ioException: IOException) { + Lg.e(TAG, "Unable to write to file!", ioException) + } + + return files + } + } +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/DialogUtils.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/DialogUtils.kt new file mode 100644 index 0000000..b4472c8 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/DialogUtils.kt @@ -0,0 +1,43 @@ +package com.webaddicted.techcleanarch.global.misc + +import android.app.Activity +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import androidx.core.content.ContextCompat + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class DialogUtil { + companion object { + private val TAG = DialogUtil::class.java.simpleName +// {START SHOW DIALOG STYLE} + /** + * show dialog with transprant background + * + * @param activity reference of activity + * @param dialog reference of dialog + */ + fun modifyDialogBounds(activity: Activity?, dialog: Dialog) { + dialog.window!!.setBackgroundDrawable( + ColorDrawable( + ContextCompat.getColor( + activity!!, + android.R.color.transparent + ) + ) + ) + dialog.window!!.decorView.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + val lp = WindowManager.LayoutParams() + val window = dialog.window + lp.copyFrom(window!!.attributes) + //This makes the dialog take up the full width + //lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.width = (dialog.context.resources.displayMetrics.widthPixels * 0.83).toInt() + // lp.height = (int) (dialog.getContext().getResources().getDisplayMetrics().heightPixels * 0.55); + window.attributes = lp + } + } +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/FileUtils.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/FileUtils.kt new file mode 100644 index 0000000..817fd7d --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/FileUtils.kt @@ -0,0 +1,465 @@ +package com.webaddicted.techcleanarch.global.misc + +import android.R.attr.data +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Environment +import android.provider.MediaStore +import android.util.DisplayMetrics +import android.view.WindowManager +import android.webkit.MimeTypeMap +import androidx.core.content.FileProvider +import java.io.* +import java.net.URL +import java.text.SimpleDateFormat +import java.util.* + + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class FileUtils { + + /** + * Method to get size of file in kb + * + * @param file + * @return + */ + fun getFileSizeInKb(file: File): Long { + return file.length() / 1024 + } + + companion object { + private val APP_FOLDER = "kotlinProject" + private val SUB_PROFILE = "/profile" + private val SEPARATOR = "/" + private val JPEG = ".jpeg" + private val PNG = ".png" + + /** + * This method is used to create application specific folder on filesystem + */ + fun createApplicationFolder() { + var f = File(Environment.getExternalStorageDirectory().toString(), File.separator + APP_FOLDER) + f.mkdirs() + f = File(Environment.getExternalStorageDirectory().toString(), File.separator + APP_FOLDER + SUB_PROFILE) + f.mkdirs() + } + + /** + * Method to return file object + * + * @return File object + */ + fun appFolder(): File { + return File(Environment.getExternalStorageDirectory().toString(), File.separator + APP_FOLDER) + } + + /** + * Method to return file from sub folder + * + * @return File object + */ + fun subFolder(): File { + return File(Environment.getExternalStorageDirectory().toString(), File.separator + APP_FOLDER + SUB_PROFILE) + } + + + fun thumbFolder(): File { + return File( + Environment.getExternalStorageDirectory().toString(), + File.separator + APP_FOLDER + SUB_PROFILE + ) + } + + + /** + * Method to get filename from url + * + * @param url + * @return + */ + fun getFileNameFromUrl(url: URL): String { + val urlString = url.file + return urlString.substring(urlString.lastIndexOf('/') + 1).split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0].split( + "#".toRegex() + ).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + } + + /** + * Method to get path from Uri + * + * @param contentUri + * @return + */ + fun getPathFromUri(context: Context, contentUri: Uri): File { + var res: String? = null + val proj = arrayOf(MediaStore.Images.Media.DATA) + val cursor = context.getContentResolver().query(contentUri, proj, null, null, null) + if (cursor?.moveToFirst()!!) { + val column_index = cursor?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) + res = cursor?.getString(column_index) + } + cursor?.close() + + return File(res) + } + + /** + * Method to save thumbnail of game image + * + * @param bitmap + * @return + */ + fun saveGameThumb(bitmap: Bitmap): File { + val filename = System.currentTimeMillis().toString() + JPEG + val sd = FileUtils.appFolder() + val dest = File(sd, filename) + try { + val out = FileOutputStream(dest) + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out) + out.flush() + out.close() + } catch (e: Exception) { + e.printStackTrace() + } + + return dest + } + + /** + * Method to save bitmap + * + * @param bitmap + * @return + */ + fun saveBitmapImage(bitmap: Bitmap): File { + val filename = System.currentTimeMillis().toString() + JPEG + val dest = File(subFolder(), filename) + try { + val out = FileOutputStream(dest) + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out) + out.flush() + out.close() + } catch (e: Exception) { + e.printStackTrace() + } + + return dest + } + + fun saveBitmapImage(bitmap: Bitmap, fileLocation: File, fileName: String): File { + val filename = fileName + "_" + System.currentTimeMillis() + JPEG + val dest = File(fileLocation, filename) + try { + val out = FileOutputStream(dest) + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out) + out.flush() + out.close() + } catch (e: Exception) { + e.printStackTrace() + } + + return dest + } + + /** + * Method to save captured pic in SD card of device + * + * @param bitmap + * @param currentDate + * @return + */ + fun storeCameraPhotoInSDCard(bitmap: Bitmap, currentDate: String): File { + val outputFile = File(Environment.getExternalStorageDirectory(), "photo_$currentDate.jpg") + try { + val fileOutputStream = FileOutputStream(outputFile) + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream) + fileOutputStream.flush() + fileOutputStream.close() + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + + return outputFile + } + + /** + * Method to save bitmap + * + * @param bmp + * @return + */ + fun savebitmap(bmp: Bitmap): File? { + val extStorageDirectory = Environment.getExternalStorageDirectory().toString() + "/criiio/profile" + var outStream: OutputStream? = null + + // String temp = null; + val file = File(extStorageDirectory, System.currentTimeMillis().toString() + "_img.png") + + try { + outStream = FileOutputStream(file) + bmp.compress(Bitmap.CompressFormat.PNG, 100, outStream) + outStream.flush() + outStream.close() + + } catch (e: Exception) { + e.printStackTrace() + return null + } + + return file + } + + /** + * Method to save bitmap + * + * @param bitmap + * @param fileName + * @return + */ + fun saveBitmapImg(bitmap: Bitmap, fileName: String): File { + var filename = System.currentTimeMillis().toString() + if (fileName.endsWith(".png")) + filename = filename + PNG + else + filename = filename + JPEG + val dest = File(appFolder(), filename) + try { + val out = FileOutputStream(dest) + if (fileName.endsWith(".png")) + bitmap.compress(Bitmap.CompressFormat.PNG, 90, out) + else + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out) + + out.flush() + out.close() + } catch (e: Exception) { + e.printStackTrace() + } + + return dest + } + + + /** + * Method to return Uri from file + * + * @param file + * @return + */ + fun fileIntoUri(file: File): Uri { + return Uri.fromFile(file) + } + + /** + * Method to get Mimetype from url + * + * @param url + * @return + */ + fun getMimeType(url: String): String? { + var type: String? = null + val extension = MimeTypeMap.getFileExtensionFromUrl(url) + if (extension != null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) + } + if (type == null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension?.toLowerCase()) + } + return type + } + + fun getFileSizeInMb(file: File): Long { + // Get length of file in bytes + val fileSizeInBytes = file.length() + // Convert the bytes to Kilobytes (1 KB = 1024 Bytes) + val fileSizeInKB = fileSizeInBytes / 1024 + // Convert the KB to MegaBytes (1 MB = 1024 KBytes) + return fileSizeInKB / 1024 + } + + /** + * Method to get size of file in mb + * + * @param file + * @return + */ + fun getFileSizeInMbTest(file: File): String { + // Get length of file in bytes + val fileSizeInBytes = file.length() + // Convert the bytes to Kilobytes (1 KB = 1024 Bytes) + val fileSizeInKB = fileSizeInBytes / 1024 + // Convert the KB to MegaBytes (1 MB = 1024 KBytes) + val fileSizeInMB = fileSizeInKB / 1024 + return if (fileSizeInMB > 0) + "$fileSizeInMB MB " + else + "$fileSizeInKB KB " + } + + /** + * Method to get filename from file + * + * @param file + * @return + */ + fun getFileName(file: File): String { + return file.path.substring(file.path.lastIndexOf("/") + 1) + } + // {START CAPTURE IMAGE PROCESS} + /** + * Method to create new file of captured image + * + * @return + * @throws IOException + */ + fun createNewCaptureFile(): File { + val mFile = File( + Environment.getExternalStorageDirectory().toString(), + File.separator + APP_FOLDER + SUB_PROFILE + File.separator + "IMG_"+System.currentTimeMillis() + ".jpg" + ) + mFile.createNewFile() + return mFile + } + /** + * + * Method to get Intent + * + * @param activity + * @param photoFile + * @return + */ + fun getCaptureImageIntent(activity: Activity, photoFile: File?): Intent { + var takePictureIntent: Intent? = null + takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + val photoURI = FileProvider.getUriForFile(activity, activity.packageName+ ".provider", photoFile!!) + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) + return takePictureIntent + } + + /** + * Method to create temporary image file + * + * @param context + * @return + * @throws IOException + */ + @Throws(IOException::class) + fun createTempImageFile(context: Context): File { + val imageFileName = "Img_" + System.currentTimeMillis() + "_" + val storageDir = context.externalCacheDir + return File.createTempFile( + imageFileName, /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ + ) + } + + + /** + * Method for sampling image + * + * @param context + * @param imagePath + * @return + */ + internal fun resamplePic(context: Context, imagePath: String): Bitmap { + + // Get device screen size information + val metrics = DisplayMetrics() + val manager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + manager.defaultDisplay.getMetrics(metrics) + + val targetH = metrics.heightPixels + val targetW = metrics.widthPixels + + // Get the dimensions of the original bitmap + val bmOptions = BitmapFactory.Options() + bmOptions.inJustDecodeBounds = true + BitmapFactory.decodeFile(imagePath, bmOptions) + val photoW = bmOptions.outWidth + val photoH = bmOptions.outHeight + + // Determine how much to scale down the image + val scaleFactor = Math.min(photoW / targetW, photoH / targetH) + + // Decode the image file into a Bitmap sized to fill the View + bmOptions.inJustDecodeBounds = false + bmOptions.inSampleSize = scaleFactor + + return BitmapFactory.decodeFile(imagePath) + } + + /** + * Helper method for saving the image. + * + * @param context The application context. + * @param image The image to be saved. + * @return The path of the saved image. + */ + internal fun saveImage(context: Context, image: Bitmap): File { + var imageFile: File? = null + var savedImagePath: String? = null + val imageFileName = "Img" + System.currentTimeMillis() + ".jpg" + imageFile = File(subFolder(), imageFileName) + savedImagePath = imageFile.absolutePath + try { + val fOut = FileOutputStream(imageFile) + image.compress(Bitmap.CompressFormat.JPEG, 100, fOut) + fOut.close() + } catch (e: Exception) { + e.printStackTrace() + } + + updateGallery(context, savedImagePath) + return imageFile + } + fun saveImage(image: Bitmap?, folderPath: File?): File { + var savedImagePath: String? = null + val timeStamp = SimpleDateFormat( + "yyyyMMdd_HHmmss", + Locale.getDefault() + ).format(Date()) + val imageFileName = "JPEG_$timeStamp.jpg" + val imageFile = File(folderPath, imageFileName) + savedImagePath = imageFile.absolutePath + try { + val fOut = FileOutputStream(imageFile) + image?.compress(Bitmap.CompressFormat.JPEG, 100, fOut) + fOut.close() + } catch (e: Exception) { + e.printStackTrace() + } + + return imageFile + } + /** + * Method to update phone gallery after capturing file + * + * @param context + * @param imagePath + */ + fun updateGallery(context: Context, imagePath: String?) { + val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) + val f = File(imagePath) + val contentUri = Uri.fromFile(f) + mediaScanIntent.data = contentUri + context.sendBroadcast(mediaScanIntent) + } + + fun getBitmapFromFile(image: File): Bitmap { + val bmOptions = BitmapFactory.Options() + return BitmapFactory.decodeFile(image.absolutePath, bmOptions) + } + } + + // {END CAPTURE IMAGE PROCESS} +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/GlobalUtilities.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/GlobalUtilities.kt new file mode 100644 index 0000000..b8c3dc6 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/GlobalUtilities.kt @@ -0,0 +1,445 @@ +package com.webaddicted.techcleanarch.global.misc + +import android.app.* +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.content.res.Resources +import android.graphics.* +import android.media.RingtoneManager +import android.os.Build +import android.text.Spannable +import android.text.SpannableString +import android.text.format.DateFormat +import android.text.style.ForegroundColorSpan +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.view.animation.AnimationUtils +import android.view.inputmethod.InputMethodManager +import android.widget.TextView +import android.widget.Toast +import androidx.core.app.NotificationCompat +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.webaddicted.data.common.NotificationData +import com.webaddicted.techcleanarch.AppApplication.Companion.context +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.global.misc.AppConstant.Companion.NOTIFICATION_CHANNEL_ID +import com.webaddicted.techcleanarch.view.splash.SplashActivity +import java.io.File +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.lang.reflect.Modifier +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class GlobalUtility { + + companion object { + /** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ + fun showToast(message: String) { + Toast.makeText(context, message, Toast.LENGTH_LONG).show() + } + + fun getDate(context: Context, mDobEtm: TextView) { + val datePickerDialog = DatePickerDialog(context, R.style.TimePicker, { view, year, month, dayOfMonth -> + var monthValue = month + 1 + var day:String = "" + var dayMonth:String = "" + if (dayOfMonth<=9) day= "0"+dayOfMonth + else day = dayOfMonth.toString() + if (monthValue<=9) dayMonth= "0"+monthValue + else dayMonth = monthValue.toString() + mDobEtm.text = + "$day/$dayMonth/$year" + }, Calendar.getInstance().get(Calendar.YEAR),Calendar.getInstance().get(Calendar.MONTH),Calendar.getInstance().get(Calendar.DATE)) + datePickerDialog.show() + } + fun getDOBDate(context: Context, mDobEtm: TextView) { + val datePickerDialog = + DatePickerDialog(context, R.style.TimePicker, { view, year, month, dayOfMonth -> + var monthValue = month + 1 + var day:String = "" + var dayMonth:String = "" + + if (dayOfMonth<=9) day= "0"+dayOfMonth + else day = dayOfMonth.toString() + if (monthValue<=9) dayMonth= "0"+monthValue + else dayMonth = monthValue.toString() + mDobEtm.text = + "$dayMonth/$day/$year" + }, Calendar.getInstance().get(Calendar.YEAR),Calendar.getInstance().get(Calendar.MONTH),Calendar.getInstance().get(Calendar.DATE)) + var calendar = Calendar.getInstance(); + calendar.add(Calendar.YEAR, -16); + datePickerDialog.getDatePicker().setMaxDate(calendar.getTimeInMillis()); + datePickerDialog.show() + } + fun timePicker(activity: Activity,timeListener: TimePickerDialog.OnTimeSetListener): TimePickerDialog { + val calendar = Calendar.getInstance() + return TimePickerDialog( + activity, + R.style.TimePicker, + timeListener, + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + DateFormat.is24HourFormat(context) + ) + } + + /** + * convert date formate + * + * @param date date any formate string + * @param inputFormat input date formate + * @param outputFormat output date formate + * @return output date formate + */ + fun dateFormate(date: String, inputFormat: String, outputFormat: String): String { + var initDate: Date? = null + try { + initDate = SimpleDateFormat(inputFormat).parse(date) + } catch (e: java.text.ParseException) { + e.printStackTrace() + } + + val formatter = SimpleDateFormat(outputFormat) + return formatter.format(initDate) + } + + /** + * convertTime formate + * + * @param timeHHMM + * @return + */ + fun timeFormat12(timeHHMM: String): String { + try { + val sdf = SimpleDateFormat("H:mm") + val dateObj = sdf.parse(timeHHMM) + + return SimpleDateFormat("K:mm: a").format(dateObj) + } catch (e: ParseException) { + e.printStackTrace() + } + return "" + } + + /** + * convertTime formate + * + * @param timeHHMM + * @return + */ + fun timeFormat24(timeHHMM: String): String { + val sdf = SimpleDateFormat("hh:mm a") + var testDate: Date? = null + try { + testDate = sdf.parse(timeHHMM) + } catch (ex: Exception) { + ex.printStackTrace() + } + + val formatter = SimpleDateFormat("HH:MM") + return formatter.format(testDate) + } +// {START HIDE SHOW KEYBOARD} + + /** + * Method to hide keyboard + * + * @param activity Context of the calling class + */ + fun hideKeyboard(activity: Activity) { + try { + val inputManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + inputManager.hideSoftInputFromWindow(activity.currentFocus?.windowToken, 0) + } catch (ignored: Exception) { + Log.d("TAG", "hideKeyboard: " + ignored.message) + } + + } + + /*** + * Show SoftInput Keyboard + * @param activity reference of current activity + */ + fun showKeyboard(activity: Activity?) { + if (activity != null) { + val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm?.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) + } + } + +// {END HIDE SHOW KEYBOARD} + + +// {START STRING TO JSON & JSON TO STRING} + + /** + * @param json json String converted by Gson to string + * @param clazz referance of class type like MyBean.class + * @param + * @return bean referance + */ + fun stringToJson(json: String, clazz: Class): T { + return GsonBuilder() + .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC) + .create().fromJson(json, clazz) + } + + /** + * @param clazz referance of any bean + * @return + */ + fun jsonToString(clazz: Class<*>): String { + return Gson().toJson(clazz) + } + + //{END STRING TO JSON & JSON TO STRING} + + + //block up when loder show on screen + /** + * handle ui + * + * @param activity + * @param view + * @param isBlockUi + */ + fun handleUI(activity: Activity, view: View, isBlockUi: Boolean) { + if (isBlockUi) { + activity.window.setFlags( + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + ) + view.visibility = View.VISIBLE + } else { + activity.window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) + view.visibility = View.GONE + } + } + + /** + * provide binding of layout + * + * @param context reference of activity + * @param layout layout + * @return viewBinding + */ + fun getLayoutBinding(context: Context?, layout: Int): ViewDataBinding { + return DataBindingUtil.inflate( + LayoutInflater.from(context), + layout, + null, false + ) + } + + /** + * @param sizeOfRandomString length of random string + * @return generate a random string + */ + fun getRandomString(sizeOfRandomString: Int): String { + val ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm" + val random = Random() + val sb = StringBuilder(sizeOfRandomString) + for (i in 0 until sizeOfRandomString) + sb.append(ALLOWED_CHARACTERS[random.nextInt(ALLOWED_CHARACTERS.length)]) + return sb.toString() + } + + + /** + * two digit random number + * + * @return random number + */ + fun getTwoDigitRandomNo(): Int { + return Random().nextInt(90) + 10 + } + + + /** + * show string in different color using spannable + * + * @param textView view + * @param txtSpannable string text + * @param starText start index of text + * @param endText end index of text + */ + fun setSpannable(textView: TextView, txtSpannable: String, starText: Int, endText: Int) { + val spannableString = SpannableString(txtSpannable) + val foregroundSpan = ForegroundColorSpan(Color.GREEN) + // BackgroundColorSpan backgroundSpan = new BackgroundColorSpan(Color.GRAY); + spannableString.setSpan(foregroundSpan, starText, endText, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + // spannableString.setSpan(backgroundSpan, starText, endText, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + textView.text = spannableString + } + + /** + * button click fade animation + * + * @param view view reference + */ + fun btnClickAnimation(view: View) { + val fadeAnimation = AnimationUtils.loadAnimation(view.context, R.anim.fade_in) + view.startAnimation(fadeAnimation) + } + + fun changeLanguage(context: Context, lancuageCode: String) { +// val languageToLoad = "en" // your language + val locale = Locale(lancuageCode) + Locale.setDefault(locale) + val config = Configuration() + config.locale = locale + context.getResources().updateConfiguration( + config, + context.getResources().getDisplayMetrics() + ) + } + + /** + * Adds a watermark on the given image. + */ + fun addWatermark(res: Resources, source: Bitmap, mScreenwidth: Int): Bitmap { + val w: Int + val h: Int + val c: Canvas + val paint: Paint + val bmp: Bitmap + val watermark: Bitmap + val matrix: Matrix + val scale: Float + val r: RectF + w = source.width + h = source.height + // Create the new bitmap + bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG or Paint.FILTER_BITMAP_FLAG) + // Copy the original bitmap into the new one + c = Canvas(bmp) + c.drawBitmap(source, 0f, 0f, paint) + // Load the watermark + watermark = BitmapFactory.decodeResource(res, R.drawable.logo) + // Scale the watermark to be approximately 40% of the source image height + scale = (h.toFloat() * 0.15 / watermark.height.toFloat()).toFloat() + val scaleX = w.toFloat() / watermark.width.toFloat() + // Create the matrix + matrix = Matrix() + matrix.postScale(scaleX, scale) + // Determine the post-scaled size of the watermark + r = RectF(0f, 0f, watermark.width.toFloat(), watermark.height.toFloat()) + matrix.mapRect(r) + // Move the watermark to the bottom right corner + matrix.postTranslate(w - r.width(), h - r.height()) + // Draw the watermark + c.drawBitmap(watermark, matrix, paint) + // Free up the bitmap memory + watermark.recycle() + return bmp + } + + fun showOfflineNotification(context: Context, title:String, description: String) { + val intent = Intent(context, SplashActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + if (intent != null) { + val pendingIntent = PendingIntent.getActivity( + context, getTwoDigitRandomNo(), intent, + PendingIntent.FLAG_ONE_SHOT + ) + val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + val notificationBuilder = NotificationCompat.Builder(context) + notificationBuilder.setSmallIcon(R.mipmap.ic_launcher_round) + notificationBuilder.setLargeIcon( + BitmapFactory.decodeResource( + context.resources, + R.mipmap.ic_launcher + ) + ) + notificationBuilder.setBadgeIconType(R.mipmap.ic_launcher_round) + notificationBuilder.setContentTitle(title) + if (description != null) { + notificationBuilder.setContentText(description) + notificationBuilder.setStyle( + NotificationCompat.BigTextStyle().bigText(description) + ) + } + notificationBuilder.setAutoCancel(true) + notificationBuilder.setSound(defaultSoundUri) + notificationBuilder.setVibrate(longArrayOf(1000, 1000)) + notificationBuilder.setContentIntent(pendingIntent) + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val importance = NotificationManager.IMPORTANCE_HIGH + val notificationChannel = NotificationChannel( + NOTIFICATION_CHANNEL_ID, + "NOTIFICATION_CHANNEL_NAME", + importance + ) + notificationChannel.enableLights(true) + notificationChannel.lightColor = Color.RED + notificationChannel.enableVibration(true) + notificationChannel.vibrationPattern = longArrayOf(1000, 1000) + assert(notificationManager != null) + notificationBuilder.setChannelId(NOTIFICATION_CHANNEL_ID) + notificationManager.createNotificationChannel(notificationChannel) + } + notificationManager.notify( + getTwoDigitRandomNo()/*Id of Notification*/, + notificationBuilder.build() + ) + } + } + fun getIntentForPush( + ctx: Context, + mNotificationData: NotificationData? + ): Intent? { + var mIntent: Intent? = null + if (mNotificationData != null) { +// if (mPrefMgr.getUserId() != null && !mPrefMgr.getUserId().isEmpty()) { + mIntent = Intent(ctx, SplashActivity::class.java) +// mIntent.putExtra( +// AppConstant.NOTIFICATION_GAME_ID, +// String.valueOf(mNotificationData!!.getId()) +// ) +// mIntent.putExtra(AppConstant.NOTIFICATION_CODE, mNotificationData!!.getType()) + mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) +// } + } + return mIntent + } + fun captureScreen(activity: Activity) { + val v: View = activity.getWindow().getDecorView().getRootView() + v.isDrawingCacheEnabled = true + val bmp: Bitmap = Bitmap.createBitmap(v.drawingCache) + v.isDrawingCacheEnabled = false + try { + val sd = FileUtils.appFolder() + val dest = File(sd, "SCREEN" + + System.currentTimeMillis() + ".png") + val fos = FileOutputStream(dest) + bmp.compress(Bitmap.CompressFormat.PNG, 100, fos) + fos.flush() + fos.close() + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + } + + } + + +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/Lg.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/Lg.kt new file mode 100644 index 0000000..a6da498 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/Lg.kt @@ -0,0 +1,203 @@ +package com.webaddicted.kotlinproject.global.common + +/** + * Helper class for a list (or tree) of LoggerNodes. + * + * + * + * When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for [android.util.Log]. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode. + */ +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class Lg { + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + companion object { + val NONE = -1 + val VERBOSE = android.util.Log.VERBOSE + val DEBUG = android.util.Log.DEBUG + val INFO = android.util.Log.INFO + val WARN = android.util.Log.WARN + val ERROR = android.util.Log.ERROR + val ASSERT = android.util.Log.ASSERT + + var ISDEBUG = true + + /** + * Instructs the LogNode to print the log result provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the result being logged. Verbose, Error, etc. + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @JvmOverloads + fun println(priority: Int, tag: String, msg: String?, tr: Throwable? = null) { + try { + if (ISDEBUG) { + when (priority) { + VERBOSE -> android.util.Log.v(tag, msg!!) + DEBUG -> android.util.Log.d(tag, msg!!) + INFO -> android.util.Log.i(tag, msg!!) + WARN -> android.util.Log.w(tag, msg!!) + ERROR -> android.util.Log.e(tag, msg!!) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @JvmOverloads + fun v(tag: String, msg: String, tr: Throwable? = null) { + println(VERBOSE, tag, msg, tr) + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @JvmOverloads + fun d(tag: String, msg: String, tr: Throwable? = null) { + println(DEBUG, tag, msg, tr) + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @JvmOverloads + fun i(tag: String, msg: String, tr: Throwable? = null) { + println(INFO, tag, msg, tr) + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @JvmOverloads + fun w(tag: String, msg: String?, tr: Throwable? = null) { + println(WARN, tag, msg, tr) + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + fun w(tag: String, tr: Throwable) { + w(tag, null, tr) + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @JvmOverloads + fun e(tag: String, msg: String, tr: Throwable? = null) { + println(ERROR, tag, msg, tr) + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @JvmOverloads + fun wtf(tag: String, msg: String?, tr: Throwable? = null) { + println(ASSERT, tag, msg, tr) + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + fun wtf(tag: String, tr: Throwable) { + wtf(tag, null, tr) + } + } +} +/** + * Instructs the LogNode to print the log result provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the result being logged. Verbose, Error, etc. + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ +/** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ +/** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ +/** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ +/** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ +/** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ +/** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log result. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/MediaPickerUtils.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/MediaPickerUtils.kt new file mode 100644 index 0000000..76e3c04 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/MediaPickerUtils.kt @@ -0,0 +1,154 @@ +package com.webaddicted.techcleanarch.global.misc + +import android.Manifest +import android.app.Activity +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import com.webaddicted.kotlinproject.global.common.CompressImage +import com.webaddicted.kotlinproject.global.common.Lg +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.global.annotationdef.MediaPickerType +import com.webaddicted.techcleanarch.global.common.MediaPickerHelper +import java.io.File +import java.util.ArrayList +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class MediaPickerUtils { + private var captureImageFile: File? = null + private val TAG = MediaPickerUtils::class.java.simpleName + val REQUEST_CAMERA_VIDEO = 5000 + val REQUEST_SELECT_FILE_FROM_GALLERY = 5002 + private var mImagePickerListener: ImagePickerListener? = null + var mMimeTypes = arrayOf("image/jpeg", "image/png", "image/jpg") + var mVideoMimeTypes = arrayOf("video/3gp", "video/mpeg", "video/avi", "video/mp4") + var mImageMimeTypes = arrayOf("image/jpeg", "image/png", "image/jpg") + private var mfilePath: File? = null + + /** + * select media option + * @param activity + * @param fileType + * @param imagePickerListener + */ + fun selectMediaOption( + activity: Activity, + @MediaPickerType.MediaType fileType: Int, + filePath: File, + imagePickerListener: ImagePickerListener + ) { + mImagePickerListener = imagePickerListener + mfilePath = filePath + checkPermission(activity, fileType) + } + + /** + * check file storage permission + * + * @param activity + * @param fileType + */ + private fun checkPermission(activity: Activity, @MediaPickerType.MediaType fileType: Int) { + val locationList = ArrayList() + locationList.add(Manifest.permission.READ_EXTERNAL_STORAGE) + locationList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) + locationList.add(Manifest.permission.CAMERA) + PermissionHelper.requestMultiplePermission( + activity, + locationList, + object : PermissionHelper.Companion.PermissionListener { + override fun onPermissionGranted(mCustomPermission: List) { + FileUtils.createApplicationFolder() + selectMediaType(activity, fileType) + } + + override fun onPermissionDenied(mCustomPermission: List) { + + } + }) + } + + private fun selectMediaType(activity: Activity, @MediaPickerType.MediaType fileType: Int) { + var intent: Intent? = null + when (fileType) { + // capture image for native camera + MediaPickerType.CAPTURE_IMAGE -> { + captureImageFile = FileUtils.createNewCaptureFile() + val intent = FileUtils.getCaptureImageIntent(activity, captureImageFile) + activity.startActivityForResult(intent, REQUEST_CAMERA_VIDEO) + } + // pick image from gallery + MediaPickerType.SELECT_IMAGE -> { + intent = Intent() + intent.type = "image/*" + intent.putExtra(Intent.EXTRA_MIME_TYPES, mImageMimeTypes) + intent.action = Intent.ACTION_GET_CONTENT + activity.startActivityForResult( + Intent.createChooser( + intent, + activity.resources.getString(R.string.select_picture) + ), REQUEST_SELECT_FILE_FROM_GALLERY + ) + } + else -> throw IllegalStateException("Unexpected value: $fileType") + } + } + + fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { + var file: MutableList = ArrayList() + var compressedFiles: File? = null + when (requestCode) { + REQUEST_CAMERA_VIDEO -> { + var bitmap: Bitmap? = null + if (data?.extras != null) { + bitmap = data.extras!!.get("data") as Bitmap? + val originalFile = FileUtils.saveImage(bitmap, mfilePath) + file.add(originalFile) + } else if (data?.data != null) { + // in case of record video + file = MediaPickerHelper().getData(activity, data) + } + if (file.size==0) file.add(captureImageFile!!) + mImagePickerListener!!.imagePath(file) + } + REQUEST_SELECT_FILE_FROM_GALLERY -> { + val files = MediaPickerHelper().getData(activity, data) + for (i in files.indices) { + val filePath = files.get(i).toString() + filePath.substring(filePath.lastIndexOf(".") + 1) + if (filePath.contains(mMimeTypes[0]) || + filePath.contains(mMimeTypes[1]) || + filePath.contains(mMimeTypes[2]) + ) { + compressedFiles = + CompressImage.compressImage(activity, files.get(i).toString()) + Lg.d( + TAG, + "onActivityResult: old Image - " + FileUtils.getFileSizeInMbTest( + files.get(i) + ) + + "\n compress image - " + FileUtils.getFileSizeInMbTest( + compressedFiles + ) + ) + files.set(i, compressedFiles) + } + } + mImagePickerListener!!.imagePath(files) + } + } + refreshMedia(activity) + } + private fun refreshMedia(activity: Activity) { + if(captureImageFile!=null && captureImageFile!!.exists()) { + val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) + intent.data = Uri.fromFile(captureImageFile) + activity.sendBroadcast(intent) + } + } + interface ImagePickerListener { + fun imagePath(filePath: List) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/PermissionHelper.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/PermissionHelper.kt new file mode 100644 index 0000000..b90318c --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/PermissionHelper.kt @@ -0,0 +1,246 @@ +package com.webaddicted.techcleanarch.global.misc + +import android.app.Activity +import android.app.AlertDialog +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.annotation.NonNull +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.webaddicted.techcleanarch.R +import java.util.* + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class PermissionHelper { + companion object { + private val PERMISSION_CODE = 1212 + // private var mActivity: Activity? = null + private var mCustomPermission: List? = null + private var mPerpermissionListener: PermissionListener? = null + + /** + * Check if version is marshmallow and above. + * Used in deciding to ask runtime permission + * check single permission + * + * @param permissionListener is describe permission status + * @param permissions is single permission + */ + fun requestSinglePermission(activity: Activity, @NonNull permissions: String, @NonNull permissionListener: PermissionListener) { + mPerpermissionListener = permissionListener + mCustomPermission = Arrays.asList(*arrayOf(permissions)) + if (Build.VERSION.SDK_INT >= 23) { + if (ActivityCompat.checkSelfPermission(activity, permissions) !== PackageManager.PERMISSION_GRANTED) { + // askRequestPermissions(new String[]{permissions}); + ActivityCompat.requestPermissions(activity, arrayOf(permissions), PERMISSION_CODE) + return + } + } + permissionListener.onPermissionGranted(listOf(permissions)) + } + + /** + * Check if version is marshmallow and above. + * Used in deciding to ask runtime permission + * NO of permission check a at time. + * + * @param permissionListener is describe permission status + * @param permissions is bundle of all permission + */ + fun requestMultiplePermission(activity: Activity, @NonNull permissions: List, @NonNull permissionListener: PermissionListener) { + mPerpermissionListener = permissionListener + mCustomPermission = permissions + if (Build.VERSION.SDK_INT >= 23) { + val listPermissionsAssign = ArrayList() + for (per in permissions) { + if (ContextCompat.checkSelfPermission(activity, per) !== PackageManager.PERMISSION_GRANTED) + listPermissionsAssign.add(per) + } + if (!listPermissionsAssign.isEmpty()) { + ActivityCompat.requestPermissions( + activity, + listPermissionsAssign.toTypedArray(), + PERMISSION_CODE + ) + return + } + } + permissionListener.onPermissionGranted(permissions) + } + + fun onRequestPermissionsResult(activity: Activity, @NonNull requestCode: Int, @NonNull permissions: Array, @NonNull grantResults: IntArray) { + when (requestCode) { + PERMISSION_CODE -> { + val listPermissionsNeeded = + mCustomPermission + val perms = HashMap() + if (listPermissionsNeeded != null) { + for (permission in listPermissionsNeeded) { + perms[permission] = PackageManager.PERMISSION_GRANTED + } + } + if (grantResults.size > 0) { + for (i in permissions.indices) + perms[permissions[i]] = grantResults[i] + var isAllGranted = true + if (listPermissionsNeeded != null) { + for (permission in listPermissionsNeeded) { + if (perms[permission] == PackageManager.PERMISSION_DENIED) { + isAllGranted = false + break + } + } + } + if (isAllGranted) { + mCustomPermission?.let { mPerpermissionListener?.onPermissionGranted(it) } + } else { + var shouldRequest = false + if (listPermissionsNeeded != null) { + for (permission in listPermissionsNeeded) { + if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { + shouldRequest = true + break + } + } + } + if (shouldRequest) { + ifCancelledAndCanRequest(activity) + } else { + //permission is denied (and never ask again is checked) + //shouldShowRequestPermissionRationale will return false + ifCancelledAndCannotRequest(activity) + } + } + } + } + } + } + + /** + * permission cancel dialog + */ + private fun ifCancelledAndCanRequest(activity: Activity) { + activity?.let { + showDialogOK( + it, "Permission required for this app, please grant all permission .", + DialogInterface.OnClickListener { dialog, which -> + when (which) { + DialogInterface.BUTTON_POSITIVE -> mCustomPermission?.let { it1 -> + mPerpermissionListener?.let { it2 -> + requestMultiplePermission( + activity, + it1, + it2 + ) + } + } + DialogInterface.BUTTON_NEGATIVE -> mCustomPermission?.let { it1 -> + mPerpermissionListener?.onPermissionDenied( + it1 + ) + } + }// proceed with logic by disabling the related features or quit the app. + }) + } + } + + /** + * forcefully stoped all permission dialog + */ + private fun ifCancelledAndCannotRequest(activity: Activity) { + activity?.let { + showDialogOK( + it, + activity?.getResources().getString( + R.string.forcefully_enable_permission + ), + DialogInterface.OnClickListener { dialog, which -> + when (which) { + DialogInterface.BUTTON_POSITIVE -> { + val intent = Intent() + intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + val uri = Uri.fromParts( + "package", + activity?.getPackageName(), + null + ) + intent.data = uri + activity?.startActivity( + intent + ) + } + DialogInterface.BUTTON_NEGATIVE -> mCustomPermission?.let { it1 -> + mPerpermissionListener?.onPermissionDenied( + it1 + ) + } + }// proceed with logic by disabling the related features or quit the app. + }) + } + } + + private fun showDialogOK(activity: Activity, message: String, okListener: DialogInterface.OnClickListener) { + AlertDialog.Builder(activity).setMessage(message).setPositiveButton( + activity.resources.getString(R.string.ok), + okListener + ).setNegativeButton(activity.resources.getString(R.string.cancel), okListener).create().show() + } + + /** + * Check multiple permission if version is marshmallow and above. + * result in form of boolean + * + * @param permissions is bundle of all permission + */ + fun checkMultiplePermission(activity: Activity, @NonNull permissions: List): Boolean { + mCustomPermission = permissions + if (Build.VERSION.SDK_INT >= 23) { + val listPermissionsAssign = ArrayList() + for (per in permissions) { + if (ContextCompat.checkSelfPermission(activity, per) !== PackageManager.PERMISSION_GRANTED) { + listPermissionsAssign.add(per) + } + } + if (!listPermissionsAssign.isEmpty()) { + return false + } + } + return true + } + + /** + * Check if version is marshmallow and above. + * Used in deciding to ask runtime permission + * check single permission + * + * @param permissions is single permission + */ + fun checkPermission(activity: Activity, @NonNull permissions: String): Boolean { + mCustomPermission = Arrays.asList(*arrayOf(permissions)) + if (Build.VERSION.SDK_INT >= 23) { + if (ActivityCompat.checkSelfPermission(activity, permissions) !== PackageManager.PERMISSION_GRANTED) + return false + } + return true + } + + interface PermissionListener { + fun onPermissionGranted(mCustomPermission: List) + + fun onPermissionDenied(mCustomPermission: List) + + } + + fun clearPermission() { +// mActivity = null + mCustomPermission = null + mPerpermissionListener = null + } + } +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/ValidationHelper.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/ValidationHelper.kt new file mode 100644 index 0000000..b318aaf --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/ValidationHelper.kt @@ -0,0 +1,23 @@ +package com.webaddicted.techcleanarch.global.misc + +import android.graphics.Color +import android.view.View +import android.widget.TextView +import com.google.android.material.snackbar.Snackbar +import com.webaddicted.techcleanarch.R +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class ValidationHelper { + companion object { + fun showSnackBar(parentLayout: View, msg: String) { + val snackBar = Snackbar.make(parentLayout, msg, Snackbar.LENGTH_SHORT) + snackBar.setActionTextColor(Color.WHITE) + val view = snackBar.view + val tv = view.findViewById(R.id.snackbar_text) as TextView + tv.setTextColor(Color.WHITE) + snackBar.show() + + } + } +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/global/misc/ViewExtension.kt b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/ViewExtension.kt new file mode 100644 index 0000000..59016e1 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/global/misc/ViewExtension.kt @@ -0,0 +1,139 @@ +package com.webaddicted.techcleanarch.global.misc + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Uri +import android.view.View +import android.widget.ImageView +import android.widget.Toast +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestBuilder +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.global.customview.CircleImageView +import java.io.File + +/** + * visible view + */ +fun View.visible() { + visibility = View.VISIBLE +} + +/** + * invisible view + */ +fun View.invisible() { + visibility = View.INVISIBLE +} + +/** + * gone view + */ +fun View.gone() { + visibility = View.GONE +} +// {START SHOW IMAGE USING GLIDE} +/** + * show local image using glide + * + * @param filePath local image file path + * @param imageLoaderType type of place holder show which is define in string + */ + +/** + * show remote image using glide + * + * @param imgUrl remote image url + * @param imageLoaderType type of place holder show which is define in string + */ +fun CircleImageView.showImage(imgUrl: String?, imageLoaderType: String) { + val drawableTypeRequest = Glide.with(context) + .load(imgUrl) + getPlaceHolder(context, drawableTypeRequest, imageLoaderType).into(this) +} + + +/** + * show remote image using glide + * + * @param imgUrl remote image url + * @param imageLoaderType type of place holder show which is define in string + */ +fun ImageView.showImage(imgUrl: String?, imageLoaderType: String) { + val drawableTypeRequest = Glide.with(context) + .load(imgUrl) + getPlaceHolder(context, drawableTypeRequest, imageLoaderType).into(this) +} + +/** + * show local image using glide + * + * @param filePath local image file path + * @param imageLoaderType type of place holder show which is define in string + */ +fun ImageView.showImage(filePath: File?, imageLoaderType: String) { + val drawableTypeRequest = Glide.with(context) + .load(Uri.fromFile(filePath)) + getPlaceHolder(context, drawableTypeRequest, imageLoaderType).into(this) +} + + +/** + * apply different type of place holder + * + * @param drawableTypeRequest + * @param imageLoaderType type of place holder show + * @param + * @return + */ +fun getPlaceHolder( + context: Context, + drawableTypeRequest: RequestBuilder, + imageLoaderType: String +): RequestBuilder { + val imageLoadersArray = context.getResources().getStringArray(R.array.image_loader) + if (imageLoadersArray[0] == imageLoaderType) { + drawableTypeRequest.error(R.drawable.logo) + drawableTypeRequest.placeholder(R.drawable.logo) + } else if (imageLoadersArray[1] == imageLoaderType) { + drawableTypeRequest.error(R.drawable.logo) + drawableTypeRequest.placeholder(R.drawable.logo) + } else { + drawableTypeRequest.error(R.color.colorAccent) + drawableTypeRequest.placeholder(R.color.colorAccent) + } + return drawableTypeRequest +} +// {END SHOW IMAGE USING GLIDE} + + +/** + * Gets network state. + * + * @return the network state + */ +fun Context.isNetworkAvailable(): Boolean { + val connMgr = + getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetwork = connMgr?.activeNetworkInfo + return activeNetwork != null && activeNetwork.isAvailable && activeNetwork.isConnected +} + +fun Context.showToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() +} + +/** + * show internet connection toast + */ +fun Context.showNoNetworkToast() { + showToast(getResources().getString(R.string.no_network_msg)) +} + +/** + * show internet connection toast + */ +fun Context.showSomwthingWrongToast() { + showToast(getResources().getString(R.string.something_went_wrong)) +} + diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/adapter/NewsAdapter.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/adapter/NewsAdapter.kt new file mode 100644 index 0000000..0d3e7ce --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/adapter/NewsAdapter.kt @@ -0,0 +1,39 @@ +package com.webaddicted.techcleanarch.view.adapter + +import androidx.databinding.ViewDataBinding +import com.webaddicted.model.news.NewsChanelRespo +import com.webaddicted.kotlinproject.view.base.BaseAdapter +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.databinding.RowChannelListBinding +import com.webaddicted.techcleanarch.global.misc.showImage + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class NewsAdapter(private var newsList: ArrayList?) : BaseAdapter() { + override fun getListSize(): Int { + if (newsList == null) return 0 + return newsList?.size!! + } + + override fun getLayoutId(viewType: Int): Int { + return R.layout.row_channel_list + } + + override fun onBindTo(rowBinding: ViewDataBinding, position: Int) { + if (rowBinding is RowChannelListBinding) { + val mRowBinding = rowBinding as RowChannelListBinding + var source = newsList?.get(position) + + mRowBinding.txtChannelName.text = source?.name + mRowBinding.txtChannelDesc.text = source?.description + val stringBuilder = "https://besticon-demo.herokuapp.com/icon?url=" + source?.url + "&size=64..64..120" + mRowBinding.imgChannelImg.showImage(stringBuilder, getPlaceHolder(0)) + } + } + + fun notifyAdapter(newsBeanList: ArrayList) { + newsList = newsBeanList + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseActivity.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseActivity.kt new file mode 100644 index 0000000..003374d --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseActivity.kt @@ -0,0 +1,185 @@ +package com.webaddicted.techcleanarch.view.base + +import android.Manifest +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.TransitionDrawable +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.view.View +import android.view.WindowManager +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import com.webaddicted.kotlinproject.global.common.Lg +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.global.common.NetworkChangeReceiver +import com.webaddicted.techcleanarch.global.misc.* +import org.koin.android.ext.android.inject +import java.io.File + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +abstract class BaseActivity : AppCompatActivity(), View.OnClickListener { + private val mediaPicker: MediaPickerUtils by inject() + + companion object { + val TAG = BaseActivity::class.java.simpleName + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + overridePendingTransition(R.anim.trans_left_in, R.anim.trans_left_out) + supportActionBar?.hide() + fullScreen() + GlobalUtility.hideKeyboard(this) + var layoutResId = getLayout() + var binding: ViewDataBinding? = null + if (layoutResId != 0) { + try { + binding = DataBindingUtil.setContentView(this, layoutResId) + initUI(binding) + } catch (e: Exception) { + e.printStackTrace() + } + } + getNetworkStateReceiver() + } + + private fun fullScreen() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val window = window + if (window != null) { + window.decorView.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + window.statusBarColor = Color.TRANSPARENT + } + } + } + + protected fun setNavigationColor(color: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window?.setNavigationBarColor(color); + } + } + + abstract fun getLayout(): Int + + abstract fun initUI(binding: ViewDataBinding) + /** + * placeholder type for image + * + * @param placeholderType position of string array placeholder + * @return + */ + protected fun getPlaceHolder(placeholderType: Int): String { + val placeholderArray = getResources().getStringArray(R.array.image_loader) + return placeholderArray[placeholderType] + } + + override fun onBackPressed() { + super.onBackPressed() + overridePendingTransition(R.anim.trans_right_in, R.anim.trans_right_out) + } + + override fun onClick(v: View) {} + + fun checkStoragePermission(): ArrayList { + val multiplePermission = ArrayList() + multiplePermission.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) + multiplePermission.add(Manifest.permission.READ_EXTERNAL_STORAGE) + multiplePermission.add(Manifest.permission.CAMERA) + return multiplePermission + } + + fun checkLocationPermission(): ArrayList { + val multiplePermission = ArrayList() + multiplePermission.add(Manifest.permission.ACCESS_FINE_LOCATION) + multiplePermission.add(Manifest.permission.ACCESS_COARSE_LOCATION) + return multiplePermission + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + PermissionHelper.onRequestPermissionsResult(this, requestCode, permissions, grantResults) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode == RESULT_OK) { + if (requestCode == mediaPicker.REQUEST_CAMERA_VIDEO || requestCode == mediaPicker.REQUEST_SELECT_FILE_FROM_GALLERY) { + mediaPicker.onActivityResult(this, requestCode, resultCode, data) + } + } + } + + fun navigateFragment(layoutContainer: Int, fragment: Fragment, isEnableBackStack: Boolean) { + val fragmentManager = supportFragmentManager + val fragmentTransaction = fragmentManager.beginTransaction() +// fragmentTransaction.setCustomAnimations(R.anim.trans_left_in, R.anim.trans_left_out, R.anim.trans_right_in, R.anim.trans_right_out) + fragmentTransaction.replace(layoutContainer, fragment) + if (isEnableBackStack) + fragmentTransaction.addToBackStack(fragment.javaClass.simpleName) + fragmentTransaction.commitAllowingStateLoss() + + } + + fun navigateAddFragment(layoutContainer: Int, fragment: Fragment, isEnableBackStack: Boolean) { + val fragmentManager = supportFragmentManager + val fragmentTransaction = fragmentManager.beginTransaction() +// fragmentTransaction.setCustomAnimations(R.anim.trans_left_in, R.anim.trans_left_out, R.anim.trans_right_in, R.anim.trans_right_out) + fragmentTransaction.add(layoutContainer, fragment) + if (isEnableBackStack) + fragmentTransaction.addToBackStack(fragment.javaClass.simpleName) + fragmentTransaction.commitAllowingStateLoss() + } + + /** + * broadcast receiver for check internet connectivity + * + * @return + */ + private fun getNetworkStateReceiver() { + NetworkChangeReceiver.isInternetAvailable(object : + NetworkChangeReceiver.ConnectivityReceiverListener { + override fun onNetworkConnectionChanged(isConnected: Boolean) { + try { + isNetworkConnected(isConnected) + } catch (exception: Exception) { + Lg.d(TAG, "getNetworkStateReceiver : " + exception.toString()) + } + } + }) + } + + abstract fun isNetworkConnected(isConnected: Boolean) + + protected fun showInternetSnackbar(internetConnected: Boolean, txtNoInternet: TextView) { + if (internetConnected) { + txtNoInternet.setText(getString(R.string.back_online)) + val color = arrayOf( + ColorDrawable(resources.getColor(R.color.red_ff090b)), + ColorDrawable(resources.getColor(R.color.green_00de4a)) + ) + val trans = TransitionDrawable(color) + txtNoInternet.background = (trans) + trans.startTransition(500) + val handler = Handler() + handler.postDelayed({ txtNoInternet.gone() }, 1300) + } else { + txtNoInternet.text = getString(R.string.no_internet_connection) + txtNoInternet.setBackgroundResource(R.color.red_ff090b) + txtNoInternet.visible() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseAdapter.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseAdapter.kt new file mode 100644 index 0000000..44566f3 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseAdapter.kt @@ -0,0 +1,82 @@ +package com.webaddicted.kotlinproject.view.base + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.NonNull +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.RecyclerView +import com.webaddicted.techcleanarch.AppApplication +import com.webaddicted.techcleanarch.R + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +abstract class BaseAdapter : RecyclerView.Adapter() { + protected val mContext = AppApplication.context + protected abstract fun getListSize(): Int? + + companion object { + private val TAG = BaseAdapter::class.java.simpleName + } + + override fun getItemCount(): Int { + return getListSize()!! + } + + protected abstract fun getLayoutId(viewType: Int): Int + + protected abstract fun onBindTo(rowBinding: ViewDataBinding, position: Int) + + @NonNull + override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val rowBindingUtil: ViewDataBinding = DataBindingUtil.inflate( + LayoutInflater.from(parent.context), + getLayoutId(viewType), + parent, + false + ) + return ViewHolder(rowBindingUtil) + } + + override fun onBindViewHolder(@NonNull holder: RecyclerView.ViewHolder, position: Int) { + if (holder is ViewHolder) + (holder as ViewHolder).binding(position) + } + + /** + * placeholder type for image + * + * @param placeholderType position of string array placeholder + * @return + */ + protected open fun getPlaceHolder(placeholderType: Int): String { + val placeholderArray = mContext.getResources().getStringArray(R.array.image_loader) + return placeholderArray[placeholderType] + } + + /** + * view holder + */ + inner class ViewHolder(private val mRowBinding: ViewDataBinding) : + RecyclerView.ViewHolder(mRowBinding.getRoot()) { + /** + * @param position current item position + */ + fun binding(position: Int) { + // sometime adapter position is -1 that case handle by position + if (getAdapterPosition() >= 0) onBindTo(mRowBinding, getAdapterPosition()) + else onBindTo(mRowBinding, position) + } + } + + protected open fun onClickListener(view: View?, position: Int){ + view?.setOnClickListener({ getClickEvent(view, position)}) + } + + protected open fun getClickEvent(view: View?, position: Int) { + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseDialog.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseDialog.kt new file mode 100644 index 0000000..01fe575 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseDialog.kt @@ -0,0 +1,47 @@ +package com.webaddicted.techcleanarch.view.base + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.DialogFragment +import com.webaddicted.techcleanarch.R + +/** + * this class help in show working process of create game & + * select player/fielder rule + */ +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +@SuppressLint("ValidFragment") +abstract class BaseDialog : DialogFragment(), View.OnClickListener { + private lateinit var mBinding: ViewDataBinding + abstract fun getLayout(): Int + protected abstract fun onViewsInitialized(binding: ViewDataBinding?, view: View) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + mBinding = DataBindingUtil.inflate(inflater, getLayout(), container, false) + return mBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + onViewsInitialized(mBinding, view) + super.onViewCreated(view, savedInstanceState) + } + + override fun onClick(v: View) { + } + + override fun onActivityCreated(arg0: Bundle?) { + super.onActivityCreated(arg0) + dialog?.window!! + .attributes.windowAnimations = R.style.DialogFadeAnimation + } +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseFragment.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseFragment.kt new file mode 100644 index 0000000..76520a7 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/base/BaseFragment.kt @@ -0,0 +1,135 @@ +package com.webaddicted.techcleanarch.view.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import com.webaddicted.network.apiutils.ApiResponse +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.global.misc.GlobalUtility +import com.webaddicted.techcleanarch.global.misc.MediaPickerUtils +import com.webaddicted.techcleanarch.global.misc.ValidationHelper +import com.webaddicted.techcleanarch.global.misc.showToast +import com.webaddicted.techcleanarch.view.dialog.LoaderDialog +import org.koin.android.ext.android.inject + +/** + * Created by Deepak Sharma on 15/1/19. + */ +abstract class BaseFragment : Fragment(), View.OnClickListener { + private lateinit var mBinding: ViewDataBinding + private var loaderDialog: LoaderDialog? = null + protected val mediaPicker: MediaPickerUtils by inject() + abstract fun getLayout(): Int + protected abstract fun onViewsInitialized(binding: ViewDataBinding?, view: View) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + mBinding = DataBindingUtil.inflate(inflater, getLayout(), container, false) + return mBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + onViewsInitialized(mBinding, view) + super.onViewCreated(view, savedInstanceState) + if (loaderDialog == null) { + loaderDialog = LoaderDialog.dialog() + loaderDialog?.isCancelable = false + } + } + + protected fun showApiLoader() { + if (loaderDialog != null) { + val fragment = fragmentManager?.findFragmentByTag(LoaderDialog.TAG) + if (fragment != null) fragmentManager?.beginTransaction()?.remove(fragment)?.commit() + loaderDialog?.show(fragmentManager, LoaderDialog.TAG) + } + } + + protected fun hideApiLoader() { + if (loaderDialog != null && loaderDialog?.isVisible!!) loaderDialog?.dismiss() + } + + protected fun apiResponseHandler(view: View, response: ApiResponse) { + when (response?.status) { + ApiResponse.Status.LOADING -> { + showApiLoader() + } + ApiResponse.Status.ERROR -> { + hideApiLoader() + if (response.errorMessage != null && response.errorMessage?.length!! > 0) + ValidationHelper.showSnackBar(view, response.errorMessage!!) + else activity?.showToast(getString(R.string.something_went_wrong)) + } + } + } + + override fun onResume() { + super.onResume() + activity?.let { GlobalUtility.hideKeyboard(it) } + } + + protected fun navigateFragment( + layoutContainer: Int, + fragment: Fragment, + isEnableBackStack: Boolean + ) { + if (activity != null) { + (activity as BaseActivity).navigateFragment( + layoutContainer, + fragment, + isEnableBackStack + ) + } + } + + protected fun navigateAddFragment( + layoutContainer: Int, + fragment: Fragment, + isEnableBackStack: Boolean + ) { + if (activity != null) { + (activity as BaseActivity).navigateAddFragment( + layoutContainer, + fragment, + isEnableBackStack + ) + } + } + + protected fun navigateChildFragment( + layoutContainer: Int, + fragment: Fragment, + isEnableBackStack: Boolean + ) { + val fragmentManager = childFragmentManager + val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.replace(layoutContainer, fragment) + if (isEnableBackStack) + fragmentTransaction.addToBackStack(fragment.javaClass.simpleName) + fragmentTransaction.commitAllowingStateLoss() + } + + override fun onClick(v: View) { + } + + + fun checkStoragePermission(): ArrayList { + return (getActivity() as BaseActivity).checkStoragePermission() + } + + fun checkLocationPermission(): ArrayList { + return (getActivity() as BaseActivity).checkLocationPermission() + } + + fun getPlaceHolder(imageLoaderPos: Int): String { + val imageLoader = getResources().getStringArray(R.array.image_loader) + return imageLoader[imageLoaderPos] + } +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/base/ScrollListener.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/base/ScrollListener.kt new file mode 100644 index 0000000..2f82039 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/base/ScrollListener.kt @@ -0,0 +1,114 @@ +package com.webaddicted.kotlinproject.view.base + +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.StaggeredGridLayoutManager +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +abstract class ScrollListener : RecyclerView.OnScrollListener { + private var mLayoutManager: RecyclerView.LayoutManager?=null + // The minimum amount of items to have below your current scroll position + // before loading more. + private var visibleThreshold = 5 + // The current offset index of data you have loaded + private var currentPage = 1 + // The total number of items in the dataset after the last load + private var previousTotalItemCount = 0 + // True if we are still waiting for the last set of data to load. + private var loading = true + // Sets the starting page index + private val startingPageIndex = 1 + + constructor(layoutManager: LinearLayoutManager) { + this.mLayoutManager = layoutManager + } + + constructor(layoutManager: GridLayoutManager) { + this.mLayoutManager = layoutManager + visibleThreshold = visibleThreshold * layoutManager.spanCount + } + + constructor(layoutManager: StaggeredGridLayoutManager) { + this.mLayoutManager = layoutManager + visibleThreshold = visibleThreshold * layoutManager.spanCount + } + + constructor(layoutMgr: RecyclerView.LayoutManager){ + if (layoutMgr is LinearLayoutManager){ + this.mLayoutManager = layoutMgr as LinearLayoutManager + }else if (layoutMgr is GridLayoutManager){ + this.mLayoutManager = layoutMgr as GridLayoutManager + visibleThreshold = visibleThreshold * layoutMgr.spanCount + }else if (layoutMgr is StaggeredGridLayoutManager){ + this.mLayoutManager = layoutMgr as StaggeredGridLayoutManager + visibleThreshold = visibleThreshold * layoutMgr.spanCount + } + } + + fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int { + var maxSize = 0 + for (i in lastVisibleItemPositions.indices) { + if (i == 0) { + maxSize = lastVisibleItemPositions[i] + } else if (lastVisibleItemPositions[i] > maxSize) { + maxSize = lastVisibleItemPositions[i] + } + } + return maxSize + } + + // This happens many times a second during a scroll, so be wary of the code you place here. + // We are given a few useful parameters to help us work out if we need to load some more data, + // but first we check if we are waiting for the previous load to finish. + override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) { + var lastVisibleItemPosition = 0 + val totalItemCount = mLayoutManager?.itemCount + if (mLayoutManager is StaggeredGridLayoutManager) { + val lastVisibleItemPositions = + (mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null) + // get maximum element within the list + lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions) + } else if (mLayoutManager is GridLayoutManager) { + lastVisibleItemPosition = (mLayoutManager as GridLayoutManager).findLastVisibleItemPosition() + } else if (mLayoutManager is LinearLayoutManager) { + lastVisibleItemPosition = (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition() + } + // If the total item count is zero and the previous isn't, assume the + // list is invalidated and should be reset back to initial state + if (totalItemCount!! < previousTotalItemCount) { + this.currentPage = this.startingPageIndex + this.previousTotalItemCount = totalItemCount + if (totalItemCount == 0) { + this.loading = true + } + } + // If it’s still loading, we check to see if the dataset count has + // changed, if so we conclude it has finished loading and update the current page + // number and total item count. + if (loading && totalItemCount > previousTotalItemCount) { + loading = false + previousTotalItemCount = totalItemCount + } + // If it isn’t currently loading, we check to see if we have breached + // the visibleThreshold and need to reload more data. + // If we do need to reload some more data, we execute onLoadMore to fetch the data. + // threshold should reflect how many total columns there are too + if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) { + currentPage++ + onLoadMore(currentPage, totalItemCount, view) + loading = true + } + } + + // Call this method whenever performing new searches + fun resetState() { + this.currentPage = this.startingPageIndex + this.previousTotalItemCount = 0 + this.loading = true + } + + // Defines the process for actually loading more data based on page + abstract fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView) +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/dialog/LoaderDialog.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/dialog/LoaderDialog.kt new file mode 100644 index 0000000..e765c6f --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/dialog/LoaderDialog.kt @@ -0,0 +1,35 @@ +package com.webaddicted.techcleanarch.view.dialog + +import android.view.View +import androidx.databinding.ViewDataBinding +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.databinding.DialogLoaderBinding +import com.webaddicted.techcleanarch.global.misc.DialogUtil +import com.webaddicted.techcleanarch.view.base.BaseDialog +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class LoaderDialog : BaseDialog() { + private lateinit var mBinding: DialogLoaderBinding + companion object { + val TAG = LoaderDialog::class.java.simpleName + fun dialog(): LoaderDialog { + val dialog= LoaderDialog() + return dialog + } + } + + override fun getLayout(): Int { + return R.layout.dialog_loader + } + + override fun onViewsInitialized(binding: ViewDataBinding?, view: View) { + mBinding = binding as DialogLoaderBinding + } + + override fun onResume() { + super.onResume() + DialogUtil.modifyDialogBounds(activity, dialog) + } + +} diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/home/HomeActivity.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/home/HomeActivity.kt new file mode 100644 index 0000000..f6979d9 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/home/HomeActivity.kt @@ -0,0 +1,50 @@ +package com.webaddicted.techcleanarch.view.home + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.databinding.ActivityCommonBinding +import com.webaddicted.techcleanarch.view.base.BaseActivity + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class HomeActivity : BaseActivity() { + + private lateinit var mBinding: ActivityCommonBinding + + companion object { + val TAG: String = HomeActivity::class.java.simpleName + fun newIntent(activity: Activity) { + activity.startActivity(Intent(activity, HomeActivity::class.java)) + } + } + + override fun getLayout(): Int { + return R.layout.activity_common + } + + override fun isNetworkConnected(isInternetConnected: Boolean) { + showInternetSnackbar(isInternetConnected, mBinding.txtNoInternet) + } + + override fun initUI(binding: ViewDataBinding) { + mBinding = binding as ActivityCommonBinding + navigateScreen(NewsFrm.TAG) + } + + /** + * navigate on fragment + * @param tag represent navigation activity + */ + private fun navigateScreen(tag: String) { + var frm: Fragment? = null + when (tag) { + NewsFrm.TAG -> frm = NewsFrm.getInstance(Bundle()) + } + if (frm != null) navigateFragment(R.id.container, frm, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/home/NewsFrm.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/home/NewsFrm.kt new file mode 100644 index 0000000..ceea391 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/home/NewsFrm.kt @@ -0,0 +1,125 @@ +package com.webaddicted.techcleanarch.view.home + +import android.os.Bundle +import android.view.View +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.webaddicted.model.news.NewsChanelRespo +import com.webaddicted.kotlinproject.view.base.ScrollListener +import com.webaddicted.network.apiutils.ApiResponse +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.databinding.FrmNewsBinding +import com.webaddicted.techcleanarch.global.misc.AppConstant +import com.webaddicted.techcleanarch.global.misc.gone +import com.webaddicted.techcleanarch.global.misc.visible +import com.webaddicted.techcleanarch.view.adapter.NewsAdapter +import com.webaddicted.techcleanarch.view.base.BaseFragment +import com.webaddicted.techcleanarch.viewmodel.NewsViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class NewsFrm : BaseFragment() { + private var newsList: ArrayList? = null + private lateinit var mBinding: FrmNewsBinding + private lateinit var newsAdapter: NewsAdapter + private val mViewModel: NewsViewModel by viewModel() + private var mPageCount: Int = 1 + + companion object { + val TAG = NewsFrm::class.java.simpleName + fun getInstance(bundle: Bundle): NewsFrm { + val fragment = NewsFrm() + fragment.arguments = bundle + return NewsFrm() + } + } + + override fun getLayout(): Int { + return R.layout.frm_news + } + + override fun onViewsInitialized(binding: ViewDataBinding?, view: View) { + mBinding = binding as FrmNewsBinding + init() + clickListener() + setAdapter() + } + + private fun init() { + mBinding.toolbar.imgProfile.visible() + mBinding.toolbar.txtToolbarTitle.text = resources.getString(R.string.news_channel) + mBinding.parent.setBackgroundColor(resources.getColor(R.color.grey_light)) + callApi() + } + + private fun clickListener() { + mBinding.toolbar.imgProfile.setOnClickListener(this) + mBinding.toolbar.imgBack.setOnClickListener(this) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.img_profile -> navigateScreen(ProfileFrm.TAG) + } + } + + private fun setAdapter() { + newsAdapter = NewsAdapter(newsList) + mBinding.rvNewsChannel.layoutManager = LinearLayoutManager(activity) + mBinding.rvNewsChannel.addOnScrollListener(object : + ScrollListener(mBinding.rvNewsChannel.getLayoutManager() as LinearLayoutManager) { + override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView) { + mPageCount++ + callApi() + } + }) + mBinding.rvNewsChannel.adapter = newsAdapter + } + + private fun callApi() { + mViewModel.getNewsChannelLiveData()?.observe(this, channelObserver) + mViewModel.newsChannelApi( + "https://newsapi.org/v2/sources?language=en&page=" + mPageCount + "&pageSize=" + AppConstant.PAGINATION_SIZE + "&apiKey=" + getString( + R.string.news_api_key + ) + ) + } + + private val channelObserver: Observer> by lazy { + Observer { response: ApiResponse -> handleLoginResponse(response) } + } + + private fun handleLoginResponse(response: ApiResponse) { + apiResponseHandler(mBinding.parent, response) + when (response.status) { + ApiResponse.Status.SUCCESS -> { + hideApiLoader() + if (newsList == null || newsList?.size == 0) newsList = response.data!!.sources + else newsList?.addAll(response.data!!.sources) + newsAdapter.notifyAdapter(newsList!!) + if (newsList == null || newsList?.size == 0) + mBinding.txtNoDataFound.visible() + else mBinding.txtNoDataFound.gone() + } + } + } + + /** + * navigate on fragment + * @param tag represent navigation activity + */ + private fun navigateScreen(tag: String) { + var frm: Fragment? = null + when (tag) { + ProfileFrm.TAG -> frm = ProfileFrm.getInstance(Bundle()) + } + if (frm != null) navigateAddFragment(R.id.container, frm, true) + } +} + + diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/home/ProfileFrm.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/home/ProfileFrm.kt new file mode 100644 index 0000000..965d7e9 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/home/ProfileFrm.kt @@ -0,0 +1,71 @@ +package com.webaddicted.techcleanarch.view.home + +import android.os.Bundle +import android.view.View +import androidx.databinding.ViewDataBinding +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.databinding.FrmProfileBinding +import com.webaddicted.techcleanarch.global.annotationdef.MediaPickerType +import com.webaddicted.techcleanarch.global.misc.FileUtils +import com.webaddicted.techcleanarch.global.misc.MediaPickerUtils +import com.webaddicted.techcleanarch.global.misc.showImage +import com.webaddicted.techcleanarch.global.misc.visible +import com.webaddicted.techcleanarch.view.base.BaseFragment +import java.io.File +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class ProfileFrm : BaseFragment() { + private lateinit var mBinding: FrmProfileBinding + + companion object { + val TAG = ProfileFrm::class.java.simpleName + fun getInstance(bundle: Bundle): ProfileFrm { + val fragment = ProfileFrm() + fragment.arguments = bundle + return ProfileFrm() + } + } + + override fun getLayout(): Int { + return R.layout.frm_profile + } + + override fun onViewsInitialized(binding: ViewDataBinding?, view: View) { + mBinding = binding as FrmProfileBinding + init() + clickListener() + } + + private fun init() { + mBinding.toolbar.imgBack.visible() + mBinding.toolbar.txtToolbarTitle.text = resources.getString(R.string.my_profile) + } + + private fun clickListener() { + mBinding.btnCaptureImage.setOnClickListener(this) + mBinding.btnPickImage.setOnClickListener(this) + mBinding.toolbar.imgBack.setOnClickListener(this) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.btn_capture_image -> requestCamera(MediaPickerType.CAPTURE_IMAGE) + R.id.btn_pick_image -> requestCamera(MediaPickerType.SELECT_IMAGE) + R.id.img_back -> activity?.onBackPressed() + } + } + private fun requestCamera(@MediaPickerType.MediaType captureImage: Int) { + mediaPicker.selectMediaOption(activity!!, + captureImage, + FileUtils.subFolder(), + object : MediaPickerUtils.ImagePickerListener { + override fun imagePath(filePath: List) { + mBinding.imgProfile.showImage(filePath[0], getPlaceHolder(0)) + } + }) + } + +} + diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertDialogListener.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertDialogListener.kt new file mode 100644 index 0000000..5e365b5 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertDialogListener.kt @@ -0,0 +1,8 @@ +package com.webaddicted.techcleanarch.view.interfaces +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +interface AlertDialogListener { + fun okClick() + fun cancelClick() +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertRetryDialogListener.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertRetryDialogListener.kt new file mode 100644 index 0000000..f74f761 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/interfaces/AlertRetryDialogListener.kt @@ -0,0 +1,9 @@ +package com.webaddicted.techcleanarch.view.interfaces +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +interface AlertRetryDialogListener { + fun okClick() + fun okRetry() + fun cancelClick() +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/view/splash/SplashActivity.kt b/app/src/main/java/com/webaddicted/techcleanarch/view/splash/SplashActivity.kt new file mode 100644 index 0000000..cec993d --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/view/splash/SplashActivity.kt @@ -0,0 +1,43 @@ +package com.webaddicted.techcleanarch.view.splash + +import android.os.Handler +import androidx.databinding.ViewDataBinding +import com.webaddicted.techcleanarch.R +import com.webaddicted.techcleanarch.databinding.ActivitySplashBinding +import com.webaddicted.techcleanarch.global.misc.AppConstant +import com.webaddicted.techcleanarch.view.base.BaseActivity +import com.webaddicted.techcleanarch.view.home.HomeActivity +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class SplashActivity : BaseActivity() { + val TAG: String = SplashActivity::class.java.simpleName + private lateinit var mBinding: ActivitySplashBinding + override fun getLayout(): Int { + return R.layout.activity_splash + } + + override fun isNetworkConnected(isInternetConnected: Boolean) { + showInternetSnackbar(isInternetConnected, mBinding.txtNoInternet) + } + + override fun initUI(binding: ViewDataBinding) { + mBinding = binding as ActivitySplashBinding + init() + setNavigationColor(resources.getColor(R.color.app_color)) + } + + private fun init() { + navigateToNext() + } + + /** + * navigate to welcome activity after Splash timer Delay + */ + private fun navigateToNext() { + Handler().postDelayed({ + HomeActivity.newIntent(this) + finish() + }, AppConstant.SPLASH_DELAY) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/viewmodel/BaseViewModel.kt b/app/src/main/java/com/webaddicted/techcleanarch/viewmodel/BaseViewModel.kt new file mode 100644 index 0000000..617a81a --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/viewmodel/BaseViewModel.kt @@ -0,0 +1,15 @@ +package com.webaddicted.techcleanarch.viewmodel + +import androidx.lifecycle.ViewModel +import com.webaddicted.data.sharedPref.PreferenceMgr +import com.webaddicted.database.dao.UserInfoDao +import org.koin.core.KoinComponent +import org.koin.core.inject + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +open class BaseViewModel :ViewModel(), KoinComponent { + protected val preferenceMgr: PreferenceMgr by inject() + protected val userInfoDao: UserInfoDao by inject() +} \ No newline at end of file diff --git a/app/src/main/java/com/webaddicted/techcleanarch/viewmodel/NewsViewModel.kt b/app/src/main/java/com/webaddicted/techcleanarch/viewmodel/NewsViewModel.kt new file mode 100644 index 0000000..6b9dd45 --- /dev/null +++ b/app/src/main/java/com/webaddicted/techcleanarch/viewmodel/NewsViewModel.kt @@ -0,0 +1,22 @@ +package com.webaddicted.techcleanarch.viewmodel + +import androidx.lifecycle.MutableLiveData +import com.webaddicted.data.repo.NewsRepository +import com.webaddicted.model.news.NewsChanelRespo +import com.webaddicted.network.apiutils.ApiResponse + +/** + * Created by Deepak Sharma(webaddicted) on 15/01/20. + */ +class NewsViewModel( private val projectRepository: NewsRepository) : BaseViewModel(){ + private var channelResponse = MutableLiveData>() + + fun getNewsChannelLiveData(): MutableLiveData> { + return channelResponse + } + + fun newsChannelApi(strUrl: String) { + projectRepository.getNewsChannel(strUrl, channelResponse) + } + +} \ No newline at end of file diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..20ac243 --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..4ebd4e2 --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/trans_left_in.xml b/app/src/main/res/anim/trans_left_in.xml new file mode 100644 index 0000000..3b0b2ac --- /dev/null +++ b/app/src/main/res/anim/trans_left_in.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/anim/trans_left_out.xml b/app/src/main/res/anim/trans_left_out.xml new file mode 100644 index 0000000..0774192 --- /dev/null +++ b/app/src/main/res/anim/trans_left_out.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/anim/trans_right_in.xml b/app/src/main/res/anim/trans_right_in.xml new file mode 100644 index 0000000..d698f7f --- /dev/null +++ b/app/src/main/res/anim/trans_right_in.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/anim/trans_right_out.xml b/app/src/main/res/anim/trans_right_out.xml new file mode 100644 index 0000000..c60a942 --- /dev/null +++ b/app/src/main/res/anim/trans_right_out.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/circle_white.xml b/app/src/main/res/drawable/circle_white.xml new file mode 100644 index 0000000..4524c43 --- /dev/null +++ b/app/src/main/res/drawable/circle_white.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000..827cde0 --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_profile.xml b/app/src/main/res/drawable/ic_profile.xml new file mode 100644 index 0000000..84a604d --- /dev/null +++ b/app/src/main/res/drawable/ic_profile.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7a925a819d4c2a1cd544a9ed985b2c93c4b5d8d8 GIT binary patch literal 10915 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�)Z=0H-`^>!e7I0uue~yct+tRGNU{B$%a_c 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_common.xml b/app/src/main/res/layout/activity_common.xml new file mode 100644 index 0000000..ad0a7bd --- /dev/null +++ b/app/src/main/res/layout/activity_common.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..b5d2698 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_loader.xml b/app/src/main/res/layout/dialog_loader.xml new file mode 100644 index 0000000..6bdf56e --- /dev/null +++ b/app/src/main/res/layout/dialog_loader.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/frm_news.xml b/app/src/main/res/layout/frm_news.xml new file mode 100644 index 0000000..9af5666 --- /dev/null +++ b/app/src/main/res/layout/frm_news.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/frm_profile.xml b/app/src/main/res/layout/frm_profile.xml new file mode 100644 index 0000000..badb73e --- /dev/null +++ b/app/src/main/res/layout/frm_profile.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + +