diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aed8724 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.idea +.gradle +#jcenter.settings.gradle +/local.properties +.DS_Store +/build +/captures +.externalNativeBuild \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2f4d471 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# Kotlin Bootstrap [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) + +## Meet Kotlin Bootstrap by Cleveroad + +This is tutorial about android bootstrap library. + +## Setup and usage +### Installation +by project gradle : +```groovy +ext { + + bootstrapVersion = "2.0.0" + + bootstrapDependencies = [ + auth : "com.cleveroad.bootstrap:kotlin-auth:$bootstrapVersion", + core : "com.cleveroad.bootstrap:kotlin-core:$bootstrapVersion", + ext : "com.cleveroad.bootstrap:kotlin-ext:$bootstrapVersion" + thumbnails : "com.cleveroad.bootstrap:kotlin-ffmpeg-thumbnails:$bootstrapVersion" + video_compress: "com.cleveroad.bootstrap:kotlin-ffmpeg-video-compress:$bootstrapVersion" + gps : "com.cleveroad.bootstrap:kotlin-gps:$bootstrapVersion" + permission : "com.cleveroad.bootstrap:kotlin-permissionrequest:$bootstrapVersion" + phone_input : "com.cleveroad.bootstrap:kotlin-phone-input:$bootstrapVersion" + validators : "com.cleveroad.bootstrap:kotlin-validators:$bootstrapVersion", + rxBus : "com.cleveroad.bootstrap:kotlin-rx-bus:$bootstrapVersion", + ] +} +``` + +by app gradle : +```groovy + +dependencies { + + // Bootstrap + implementation bootstrapDependencies.auth + implementation bootstrapDependencies.core + implementation bootstrapDependencies.ext + implementation bootstrapDependencies.thumbnails + implementation bootstrapDependencies.video_compress + implementation bootstrapDependencies.gps + implementation bootstrapDependencies.permission + implementation bootstrapDependencies.phone_input + implementation bootstrapDependencies.validators + implementation bootstrapDependencies.rxBus +} +``` + +### Usage ### + +- [Kotlin Auth] +- [Kotlin Core] +- [Kotlin Extension] +- [Kotlin FFMpeg Thumbnails] +- [Kotlin FFMpeg Video Compress] +- [Kotlin GPS] +- [Kotlin Permission Request] +- [Kotlin Phone Input] +- [Kotlin RxBus] +- [Kotlin Validators] + +### Support ### +If you have any questions, issues or propositions, please create a new issue in this repository. + +If you want to hire us, send an email to sales@cleveroad.com or fill the form on contact page + +Follow us: + +[![Awesome](/images/social/facebook.png)](https://www.facebook.com/cleveroadinc/) [![Awesome](/images/social/twitter.png)](https://twitter.com/cleveroadinc) [![Awesome](/images/social/google.png)](https://plus.google.com/+CleveroadInc) [![Awesome](/images/social/linkedin.png)](https://www.linkedin.com/company/cleveroad-inc-) [![Awesome](/images/social/youtube.png)](https://www.youtube.com/channel/UCFNHnq1sEtLiy0YCRHG2Vaw) +
+ +### License ### +* * * + The MIT License (MIT) + + Copyright (c) 2016 Cleveroad Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +[Kotlin Auth]: /kotlin-auth +[Kotlin Core]: /kotlin-core +[Kotlin Extension]: /kotlin-ext +[Kotlin FFMpeg Thumbnails]: /kotlin-ffmpeg-thumbnails +[Kotlin FFMpeg Video Compress]: /kotlin-ffmpeg-video-compress +[Kotlin GPS]: /kotlin-gps +[Kotlin Permission Request]: /kotlin-permissionrequest +[Kotlin Phone Input]: /kotlin-phone-input +[Kotlin RxBus]: /kotlin-rx-bus +[Kotlin Validators]: /kotlin-validators \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..ddcd60b --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,8 @@ +*.iml +.idea +.gradle +/local.properties +.DS_Store +/build +/captures +.externalNativeBuild \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..c065048 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,82 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion as Integer + buildToolsVersion rootProject.ext.buildToolsVersion as String + defaultConfig { + applicationId "com.cleveroad.bootstrap.kotlin" + minSdkVersion 21 + targetSdkVersion rootProject.ext.compileSdkVersion as Integer + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + all { + buildConfigField "String", "GOOGLE_CLIENT_ID", '"YOUR_CLIENT_ID"' + buildConfigField "String", "TWITTER_CONSUMER_KEY", '"YOUR_CONSUMER_KEY"' + buildConfigField "String", "TWITTER_REDIRECT_URL", '"_REDIRECT_URL"' + buildConfigField "String", "TWITTER_CONSUMER_SECRET", '"YOUR_CONSUMER_SECRET"' + buildConfigField "String", "LINKEDIN_CLIENT_ID", '"YOUR_CLIENT_ID"' + buildConfigField "String", "LINKEDIN_CLIENT_SECRET", '"YOUR_CLIENT_SECRET"' + buildConfigField "String", "LINKEDIN_REDIRECT_URL", '"https://www.cleveroad.com/"' + } + + debug { + minifyEnabled false + debuggable true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + + release { + minifyEnabled false + debuggable false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + // tests + testImplementation testsDependencies.junit + testImplementation testsDependencies.runner + testImplementation(testsDependencies.espresso_core) { + exclude group: 'com.android.support', module: 'support-annotations' + } + + // Google Auth + implementation playServiceDependencies.play_services_auth + + // Facebook Auth + implementation facebookDependencies.login + + // common + implementation supportDependencies.appCompat + implementation supportDependencies.design + implementation kotlinDependencies.jdk + + // rx + implementation rxjava2Dependencies.rxjava + + // bootstrap + implementation project(':kotlin-phone-input') + implementation project(':kotlin-validators') + implementation project(':kotlin-core') + implementation project(':kotlin-ext') + implementation project(':kotlin-gps') + implementation project(':kotlin-ffmpeg-thumbnails') + implementation project(':kotlin-ffmpeg-video-compress') + implementation project(':kotlin-permissionrequest') + implementation project(':kotlin-auth') + +} + +repositories { + mavenCentral() +} diff --git a/app/credentials.json b/app/credentials.json new file mode 100644 index 0000000..fa2ca13 --- /dev/null +++ b/app/credentials.json @@ -0,0 +1,10 @@ +{ + "web": { + "client_id": "YOUR_CLIENT_ID", + "project_id": "PROJECT_ID", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "CLIENT_SECRET" + } +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..58213ad --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,29 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/horobets_stanislav_cr/Android/Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# 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 + +## libphonenumber +#-keep class com.google.i18n.phonenumbers.repackaged.** +#-dontwarn com.google.i18n.phonenumbers.repackaged.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8f6b842 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/cleveroad/MainActivity.kt b/app/src/main/java/com/cleveroad/MainActivity.kt new file mode 100644 index 0000000..a12304e --- /dev/null +++ b/app/src/main/java/com/cleveroad/MainActivity.kt @@ -0,0 +1,35 @@ +package com.cleveroad + +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import com.cleveroad.auth_example.SampleAuthActivity +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_ext.setClickListenerWithDebounce +import com.cleveroad.compress_image.CompressImageActivity +import com.cleveroad.ffmpeg_example.FFMpegActivity +import com.cleveroad.gps_example.GpsActivity +import com.cleveroad.phone_example.ChooseCountryActivity +import com.cleveroad.validator_example.ValidatorActivity +import kotlinx.android.synthetic.main.activity_main.* + +class MainActivity : AppCompatActivity(), View.OnClickListener { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + setClickListenerWithDebounce(bValidationExample, bGpsExample, + bFfmpegExample, bCompressImage, bPhoneViewExample, bAuthExample) + } + + override fun onClick(v: View) { + when (v.id) { + R.id.bValidationExample -> ValidatorActivity.start(this) + R.id.bGpsExample -> GpsActivity.start(this) + R.id.bFfmpegExample -> FFMpegActivity.start(this) + R.id.bCompressImage -> CompressImageActivity.start(this) + R.id.bPhoneViewExample -> ChooseCountryActivity.start(this) + R.id.bAuthExample -> SampleAuthActivity.start(this) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/auth_example/AuthFragment.kt b/app/src/main/java/com/cleveroad/auth_example/AuthFragment.kt new file mode 100644 index 0000000..75d0174 --- /dev/null +++ b/app/src/main/java/com/cleveroad/auth_example/AuthFragment.kt @@ -0,0 +1,116 @@ +package com.cleveroad.auth_example + +import android.content.Intent +import android.os.Bundle +import android.view.View +import com.cleveroad.bootstrap.kotlin.BuildConfig +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_auth.base.AuthProxy +import com.cleveroad.bootstrap.kotlin_auth.base.AuthType +import com.cleveroad.bootstrap.kotlin_auth.base.AuthType.* +import com.cleveroad.bootstrap.kotlin_auth.facebook.FacebookAuthCallback +import com.cleveroad.bootstrap.kotlin_auth.facebook.FacebookPermission +import com.cleveroad.bootstrap.kotlin_auth.google.GoogleAuthCallback +import com.cleveroad.bootstrap.kotlin_auth.linkedin.LITE_PROFILE +import com.cleveroad.bootstrap.kotlin_auth.linkedin.LinkedInAuthCallback +import com.cleveroad.bootstrap.kotlin_auth.twitter.TwitterAuthCallback +import com.cleveroad.bootstrap.kotlin_core.ui.NO_TITLE +import com.cleveroad.bootstrap.kotlin_core.ui.NO_TOOLBAR +import com.cleveroad.bootstrap.kotlin_ext.setClickListeners +import com.cleveroad.phone_example.BaseFragment +import com.google.android.gms.common.Scopes +import kotlinx.android.synthetic.main.fragment_auth.* + +class AuthFragment : BaseFragment(), + View.OnClickListener, + GoogleAuthCallback, + FacebookAuthCallback, + TwitterAuthCallback, + LinkedInAuthCallback { + + companion object { + fun newInstance() = AuthFragment() + } + + override val viewModelClass = AuthVM::class.java + + override val layoutId = R.layout.fragment_auth + + private val authProxy = AuthProxy() + + override fun observeLiveData() = Unit + + override fun getScreenTitle() = NO_TITLE + + override fun hasToolbar() = false + + override fun getToolbarId() = NO_TOOLBAR + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + authProxy.run { + registerGoogleAuthHelper(BuildConfig.GOOGLE_CLIENT_ID, + requireContext(), + listOf(Scopes.PROFILE), + this@AuthFragment) + + registerFacebookAuthHelper(listOf(FacebookPermission.PUBLIC_PROFILE), + this@AuthFragment) + + registerTwitterAuthHelper(BuildConfig.TWITTER_CONSUMER_KEY, + BuildConfig.TWITTER_CONSUMER_SECRET, + BuildConfig.TWITTER_REDIRECT_URL, + this@AuthFragment) + + registerLinkedInAuthHelper(BuildConfig.LINKEDIN_CLIENT_ID, + BuildConfig.LINKEDIN_CLIENT_SECRET, + listOf(LITE_PROFILE), + BuildConfig.LINKEDIN_REDIRECT_URL, + this@AuthFragment) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setClickListeners(bGoogle, bFacebook, bTwitter, bLinkedIn) + } + + override fun onResume() { + super.onResume() + authProxy.connect() + } + + override fun onPause() { + authProxy.disconnect() + super.onPause() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + authProxy.onActivityResult(requestCode, resultCode, data) + } + + override fun onClick(v: View?) = authProxy.run { + when (v?.id) { + R.id.bGoogle -> auth(GOOGLE_PLUS_AUTH) + R.id.bFacebook -> auth(FACEBOOK_AUTH) + R.id.bTwitter -> auth(TWITTER_AUTH) + R.id.bLinkedIn -> auth(LINKEDIN_AUTH) + else -> Unit + } + } + + override fun onSuccess(authType: AuthType, token: String) { + showSnackBar("Auth successful: $token") + } + + override fun onFail(authType: AuthType, throwable: Throwable?) { + showSnackBar("Auth fail: ${throwable?.message}") + } + + override fun onCancel() { + showSnackBar("Auth Cancelled") + } + + override fun getActivityForResult() = activity +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/auth_example/AuthVM.kt b/app/src/main/java/com/cleveroad/auth_example/AuthVM.kt new file mode 100644 index 0000000..1f39f3a --- /dev/null +++ b/app/src/main/java/com/cleveroad/auth_example/AuthVM.kt @@ -0,0 +1,6 @@ +package com.cleveroad.auth_example + +import android.app.Application +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleViewModel + +class AuthVM(application: Application) : BaseLifecycleViewModel(application) \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/auth_example/SampleAuthActivity.kt b/app/src/main/java/com/cleveroad/auth_example/SampleAuthActivity.kt new file mode 100644 index 0000000..dc8de3d --- /dev/null +++ b/app/src/main/java/com/cleveroad/auth_example/SampleAuthActivity.kt @@ -0,0 +1,37 @@ +package com.cleveroad.auth_example + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleActivity +import com.cleveroad.bootstrap.kotlin_core.ui.BlockedCallback +import com.google.android.material.snackbar.Snackbar + +class SampleAuthActivity : BaseLifecycleActivity(), + BlockedCallback { + + companion object { + fun start(context: Context) = + context.startActivity(Intent(context, SampleAuthActivity::class.java)) + } + + override val viewModelClass = SampleAuthVM::class.java + + override val containerId = R.id.flContainer + + override val layoutId = R.layout.activity_auth + + override fun getProgressBarId() = R.id.progressBar + + override fun getSnackBarDuration() = Snackbar.LENGTH_SHORT + + override fun observeLiveData() = Unit + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + replaceFragment(AuthFragment.newInstance()) + } + + override fun onBlocked() = Unit +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/auth_example/SampleAuthVM.kt b/app/src/main/java/com/cleveroad/auth_example/SampleAuthVM.kt new file mode 100644 index 0000000..e2361ab --- /dev/null +++ b/app/src/main/java/com/cleveroad/auth_example/SampleAuthVM.kt @@ -0,0 +1,6 @@ +package com.cleveroad.auth_example + +import android.app.Application +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleViewModel + +class SampleAuthVM(application: Application) : BaseLifecycleViewModel(application) \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/compress_image/CompressImageActivity.kt b/app/src/main/java/com/cleveroad/compress_image/CompressImageActivity.kt new file mode 100644 index 0000000..761423e --- /dev/null +++ b/app/src/main/java/com/cleveroad/compress_image/CompressImageActivity.kt @@ -0,0 +1,57 @@ +package com.cleveroad.compress_image + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_core.utils.IntentUtils.Companion.createImagePickFromGallery +import com.cleveroad.bootstrap.kotlin_core.utils.storage.FileUtils +import com.cleveroad.bootstrap.kotlin_core.utils.storage.ImageUtils +import com.cleveroad.bootstrap.kotlin_permissionrequest.PermissionRequest +import com.cleveroad.bootstrap.kotlin_permissionrequest.PermissionResult +import kotlinx.android.synthetic.main.activity_compress_image.* + + +class CompressImageActivity : AppCompatActivity() { + + companion object { + private const val PICK_IMAGE_REQUEST_CODE = 10002 + private const val REQUEST_WRITE_EXTERNAL_STORAGE = 4 + fun start(context: Context) = context.startActivity(Intent(context, CompressImageActivity::class.java)) + } + + private val permissionRequest = PermissionRequest() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_compress_image) + bPickImage.setOnClickListener { + permissionRequest.request(this, REQUEST_WRITE_EXTERNAL_STORAGE, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + object : PermissionResult { + override fun onPermissionGranted() { + startActivityForResult(createImagePickFromGallery(this@CompressImageActivity), PICK_IMAGE_REQUEST_CODE) + } + }) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE_REQUEST_CODE) { + FileUtils.copyFileAndGetRealPath(this, data?.data)?.let { filePath -> + ImageUtils.compressImage(Uri.parse(filePath))?.let { + ivTestImage.setImageBitmap(it) + } + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + permissionRequest.onRequestPermissionsResult(requestCode, permissions, grantResults) + } +} diff --git a/app/src/main/java/com/cleveroad/ffmpeg_example/FFMpegActivity.kt b/app/src/main/java/com/cleveroad/ffmpeg_example/FFMpegActivity.kt new file mode 100644 index 0000000..8edebd9 --- /dev/null +++ b/app/src/main/java/com/cleveroad/ffmpeg_example/FFMpegActivity.kt @@ -0,0 +1,175 @@ +package com.cleveroad.ffmpeg_example + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_core.utils.storage.FileUtils +import com.cleveroad.bootstrap.kotlin_ext.setClickListeners +import com.cleveroad.bootstrap.kotlin_ffmpeg_thumbnails.ThumbnailsFFMpegBuilder +import com.cleveroad.bootstrap.kotlin_ffmpeg_thumbnails.model.Result +import com.cleveroad.bootstrap.kotlin_ffmpeg_video_compress.CompressVideoBuilder +import com.cleveroad.bootstrap.kotlin_permissionrequest.PermissionRequest +import com.cleveroad.bootstrap.kotlin_permissionrequest.PermissionResult +import kotlinx.android.synthetic.main.activity_ffmpeg_thumbnails.* +import kotlinx.android.synthetic.main.include_progress.* +import java.io.File + +class FFMpegActivity : AppCompatActivity(), View.OnClickListener { + + companion object { + private const val TAG = "FFMpegActivity" + private const val PICK_VIDEO = "video/*" + private const val PICK_IMAGE = "image/*" + + private const val THUMBNAILS_COUNT = 100 + private const val REQUEST_PICK_VIDEO = 10004 + private const val REQUEST_PICK_OVERLAY = 10006 + private const val REQUEST_WRITE_EXTERNAL_STORAGE = 4 + private const val DEFAULT_OUTPUT_VIDEO_EXTENSION = ".mp4" + + fun start(context: Context) = context.startActivity(Intent(context, FFMpegActivity::class.java)) + } + + private val permissionRequest = PermissionRequest() + private val photosAdapter by lazy { PhotosAdapter(applicationContext) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_ffmpeg_thumbnails) + initPhotosRV() + setClickListeners(bPickFile, bPickOverlayFile, bCreateThumbnails, bCompressVideo) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode == Activity.RESULT_OK) { + data?.data?.let { + when (requestCode) { + REQUEST_PICK_VIDEO -> tvPickedFile.text = FileUtils.getRealPath(this, it) + REQUEST_PICK_OVERLAY -> tvPickedOverlayFile.text = FileUtils.getRealPath(this, it) + } + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + permissionRequest.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + + override fun onClick(view: View) { + when (view.id) { + R.id.bPickFile -> { + requestWriteStoragePermission { + startActivityForResult(intentForPickVideo(), REQUEST_PICK_VIDEO) + } + } + R.id.bPickOverlayFile -> { + requestWriteStoragePermission { + startActivityForResult(intentForPickImage(), REQUEST_PICK_OVERLAY) + } + } + R.id.bCreateThumbnails -> { + requestWriteStoragePermission { + setProgressVisibility(true) + ThumbnailsFFMpegBuilder.with(this) + .setInput(tvPickedFile.text.toString()) + .setOutput(etFileName.text.toString()) + .setThumbnailsCount(THUMBNAILS_COUNT) + .execute({ result -> onResultThumbnails(result) }, + { error -> onError(error) }, + { progress -> onProgress(progress) }) + } + } + R.id.bCompressVideo -> { + requestWriteStoragePermission { + setProgressVisibility(true) + CompressVideoBuilder.with(this) + .setInput(tvPickedFile.text.toString()) + .setOutputPath(createTempVideoFile("temp").absolutePath) + .setApproximateVideoSizeMb(10) + .execute({ result -> onResultCompressVideo(result) }, + { error -> onError(error) }, + { progress -> onProgress(progress) }) + } + } + } + } + + private fun onResultThumbnails(result: Result) { + photosAdapter.apply { + clear() + addAll(result.thumbnails) + notifyDataSetChanged() + } + setProgressVisibility(false) + } + + private fun onResultCompressVideo(pathname: String) { + val inputSize = File(tvPickedFile.text.toString()).length() + val outputSize = File(pathname).length() + setProgressVisibility(false) + tvPickedOverlayFile.text = "Picked file size - $inputSize\n" + + "Output file size - $outputSize\n" + + "Reduce difference - ${inputSize / outputSize}" + } + + private fun onError(error: Throwable) { + setProgressVisibility(false) + error.message?.let { Log.e(TAG, it) } + } + + private fun onProgress(progress: Long) { + tvProgress.text = progress.toString() + } + + private fun intentForPickVideo() = Intent().apply { + type = PICK_VIDEO + action = Intent.ACTION_PICK + } + + private fun intentForPickImage() = Intent().apply { + type = PICK_IMAGE + action = Intent.ACTION_PICK + } + + private fun requestWriteStoragePermission(onGranted: () -> Unit) { + permissionRequest.request(this, REQUEST_WRITE_EXTERNAL_STORAGE, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + object : PermissionResult { + override fun onPermissionGranted() { + onGranted.invoke() + } + }) + } + + private fun setProgressVisibility(isVisible: Boolean) { + progressView.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE + } + + private fun initPhotosRV() { + with(rvPhotos) { + invalidate() + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + adapter = photosAdapter + } + } + + //For testing + private fun createTempVideoFile(name: String, extension: String = DEFAULT_OUTPUT_VIDEO_EXTENSION): File { + val filePath = "$name$extension" + var dest = File(cacheDir, filePath) + var fileNo = 0 + while (dest.exists()) { + fileNo++ + dest = File(cacheDir, fileNo.toString() + filePath) + } + return dest + } +} diff --git a/app/src/main/java/com/cleveroad/ffmpeg_example/PhotosAdapter.kt b/app/src/main/java/com/cleveroad/ffmpeg_example/PhotosAdapter.kt new file mode 100644 index 0000000..d57667a --- /dev/null +++ b/app/src/main/java/com/cleveroad/ffmpeg_example/PhotosAdapter.kt @@ -0,0 +1,33 @@ +package com.cleveroad.ffmpeg_example + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.recyclerview.widget.RecyclerView +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_core.ui.adapter.BaseRecyclerViewAdapter +import com.cleveroad.bootstrap.kotlin_ffmpeg_thumbnails.model.Thumbnail + +class PhotosAdapter(context: Context, data: List = listOf()) : + BaseRecyclerViewAdapter(context, data) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PhotosViewHolder.newInstance(inflater, parent, R.layout.item_photo) + + override fun onBindViewHolder(holder: PhotosViewHolder, position: Int) = holder.bind(getItem(position)) + + class PhotosViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + companion object { + fun newInstance(inflater: LayoutInflater, parent: ViewGroup?, layout: Int) = + PhotosViewHolder(inflater.inflate(layout, parent, false)) + } + + private var ivPhoto = itemView.findViewById(R.id.ivPhoto) + + fun bind(thumbnail: Thumbnail) { + ivPhoto.setImageBitmap(thumbnail.bitmap) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/gps_example/GpsActivity.kt b/app/src/main/java/com/cleveroad/gps_example/GpsActivity.kt new file mode 100644 index 0000000..fd7fb5c --- /dev/null +++ b/app/src/main/java/com/cleveroad/gps_example/GpsActivity.kt @@ -0,0 +1,88 @@ +package com.cleveroad.gps_example + +import android.content.Context +import android.content.Intent +import android.location.Location +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import com.cleveroad.bootstrap.kotlin.BuildConfig +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_gps.GpsProviderImpl +import com.cleveroad.bootstrap.kotlin_gps.GpsUiDelegate +import com.cleveroad.bootstrap.kotlin_gps.GpsUiDelegateImpl +import com.cleveroad.bootstrap.kotlin_gps.LocationProvider +import kotlinx.android.synthetic.main.activity_gps.* + +class GpsActivity : AppCompatActivity(), GpsUiDelegate.GpsUiCallback { + + companion object { + fun start(context: Context) = context.run { + startActivity(Intent(this, GpsActivity::class.java)) + } + } + + private val notification by lazy { + NotificationUtils(this).createNotification() + } + + private val gpsProvider by lazy { + GpsProviderImpl.Builder(BuildConfig.APPLICATION_ID, this, notification).apply { + dialogCallback = object : GpsProviderImpl.DialogCallback { + override fun showDialogGps(): Boolean { + Toast.makeText(this@GpsActivity, "Gps", Toast.LENGTH_LONG).show() + return false + } + + override fun showDialogPermission(): Boolean { + Toast.makeText(this@GpsActivity, "permission", Toast.LENGTH_LONG).show() + return false + } + } + }.build() + } + + private val gpsUiDelegate by lazy { + GpsUiDelegateImpl(this, gpsProvider) + } + + private val locationObserver = Observer { location -> + location?.run { tvText.text = "$latitude $longitude" } + } + + private val locationIsTracking = Observer { isTracking -> + isTracking?.let { tvIsTracking.text = it.toString() } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_gps) + LocationProvider.run { + locationLiveData.observe(this@GpsActivity, locationObserver) + isTrackingLD.observe(this@GpsActivity, locationIsTracking) + } + } + + override fun getActivity(): AppCompatActivity = this + + override fun onPause() { + super.onPause() + gpsUiDelegate.onPause() + } + + override fun onResumeFragments() { + super.onResumeFragments() + gpsUiDelegate.onResumeFragments() + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + gpsUiDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + + override fun onDestroy() { + gpsUiDelegate.onDestroy() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/gps_example/NotificationUtils.kt b/app/src/main/java/com/cleveroad/gps_example/NotificationUtils.kt new file mode 100644 index 0000000..de580c5 --- /dev/null +++ b/app/src/main/java/com/cleveroad/gps_example/NotificationUtils.kt @@ -0,0 +1,60 @@ +package com.cleveroad.gps_example + +import android.app.Notification +import android.app.Notification.VISIBILITY_PRIVATE +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.NotificationManager.IMPORTANCE_NONE +import android.app.PendingIntent.getActivity +import android.content.Context +import android.content.Context.NOTIFICATION_SERVICE +import android.content.Intent +import android.os.Build +import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES.O +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import com.cleveroad.bootstrap.kotlin.R + +class NotificationUtils(val context: Context) { + + companion object { + private const val EMPTY_STRING = "" + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createNotificationChannel(): String { + val channelId = "${context.getString(R.string.app_name)} location" + NotificationChannel(channelId, + channelId, IMPORTANCE_HIGH).apply { + importance = IMPORTANCE_NONE + lockscreenVisibility = VISIBILITY_PRIVATE + (context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager) + .createNotificationChannel(this) + } + return channelId + } + + /** + * Example create notification for foreground service + */ + @SuppressWarnings("unused") + fun createNotification(): Notification { + var channelId = EMPTY_STRING + if (SDK_INT >= O) channelId = createNotificationChannel() + return NotificationCompat.Builder(context, channelId).run { + setOngoing(true) + setSmallIcon(R.mipmap.ic_launcher) + priority = NotificationCompat.PRIORITY_MIN + setContentText(context.getString(R.string.track_location_notification)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + setCategory(Notification.CATEGORY_SERVICE) + } + setContentIntent(getActivity(context, 0, + Intent(context, GpsActivity::class.java), 0)) + build() + } + } +} + diff --git a/app/src/main/java/com/cleveroad/phone_example/BaseFragment.kt b/app/src/main/java/com/cleveroad/phone_example/BaseFragment.kt new file mode 100644 index 0000000..f8daa61 --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/BaseFragment.kt @@ -0,0 +1,26 @@ +package com.cleveroad.phone_example + +import android.content.Context +import android.view.View +import com.cleveroad.bootstrap.kotlin.BuildConfig +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleFragment +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleViewModel + + +abstract class BaseFragment : BaseLifecycleFragment() { + + override var endpoint = "" + + override var versionName = "" + + override fun getVersionsLayoutId() = View.NO_ID + + override fun getEndPointTextViewId() = View.NO_ID + + override fun getVersionsTextViewId() = View.NO_ID + + override fun isDebug() = BuildConfig.DEBUG + + override fun showBlockBackAlert() = Unit + +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/BaseTextWatcher.kt b/app/src/main/java/com/cleveroad/phone_example/BaseTextWatcher.kt new file mode 100644 index 0000000..d266d2a --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/BaseTextWatcher.kt @@ -0,0 +1,26 @@ +package com.cleveroad.phone_example + +import android.text.Editable +import android.text.TextWatcher + +/** + * Base class for implement [TextWatcher] + */ +open class BaseTextWatcher(private val actionAfterTextChanged: (text: String) -> Unit = {}, + private val actionBeforeTextChanged: (s: CharSequence?, start: Int, count: Int, after: Int) -> Unit = + { _: CharSequence?, _: Int, _: Int, _: Int -> }, + private val actionOnTextChanged: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit = + { _: CharSequence?, _: Int, _: Int, _: Int -> }) : TextWatcher { + + override fun afterTextChanged(s: Editable?) { + actionAfterTextChanged(s.toString()) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + actionBeforeTextChanged(s, start, count, after) + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + actionOnTextChanged(s, start, before, count) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/ChooseCountryActivity.kt b/app/src/main/java/com/cleveroad/phone_example/ChooseCountryActivity.kt new file mode 100644 index 0000000..93509f9 --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/ChooseCountryActivity.kt @@ -0,0 +1,43 @@ +package com.cleveroad.phone_example + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleActivity +import com.cleveroad.bootstrap.kotlin_core.ui.BlockedCallback +import com.cleveroad.bootstrap.kotlin_core.ui.NO_ID +import com.cleveroad.bootstrap.kotlin_phone_input.choose_country.ChooseCountryFragment + +class ChooseCountryActivity : BaseLifecycleActivity(), PhoneViewCallback, BlockedCallback { + + companion object { + fun start(context: Context) = context.startActivity(Intent(context, ChooseCountryActivity::class.java)) + } + + override val containerId = R.id.flContainer + + override val layoutId = R.layout.activity_choose_country + + override val viewModelClass = ChooseCountryVM::class.java + + override fun onBlocked() = Unit + + override fun getProgressBarId() = NO_ID + + override fun getSnackBarDuration() = 1000 + + override fun observeLiveData() = Unit + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + replaceFragment(PhoneViewFragment.newInstance(), false) + } + + override fun showChooseCountry() { + supportFragmentManager.fragments.firstOrNull()?.let { + replaceFragment(ChooseCountryFragment.newInstance(it, RequestCode.REQUEST_CHOOSE_COUNTRY())) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/ChooseCountryVM.kt b/app/src/main/java/com/cleveroad/phone_example/ChooseCountryVM.kt new file mode 100644 index 0000000..47b4e21 --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/ChooseCountryVM.kt @@ -0,0 +1,6 @@ +package com.cleveroad.phone_example + +import android.app.Application +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleViewModel + +class ChooseCountryVM(application: Application) : BaseLifecycleViewModel(application) \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/PhoneViewCallback.kt b/app/src/main/java/com/cleveroad/phone_example/PhoneViewCallback.kt new file mode 100644 index 0000000..ac3a258 --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/PhoneViewCallback.kt @@ -0,0 +1,7 @@ +package com.cleveroad.phone_example + +interface PhoneViewCallback { + + fun showChooseCountry() + +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/PhoneViewFragment.kt b/app/src/main/java/com/cleveroad/phone_example/PhoneViewFragment.kt new file mode 100644 index 0000000..196a560 --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/PhoneViewFragment.kt @@ -0,0 +1,101 @@ +package com.cleveroad.phone_example + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.lifecycle.Observer +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_core.ui.NO_TOOLBAR +import com.cleveroad.bootstrap.kotlin_core.utils.misc.bindInterfaceOrThrow +import com.cleveroad.bootstrap.kotlin_ext.clickWithDebounce +import com.cleveroad.bootstrap.kotlin_phone_input.choose_country.ChooseCountryFragment +import com.cleveroad.bootstrap.kotlin_phone_input.data.models.CountryAsset +import kotlinx.android.synthetic.main.fragment_phone_view.* + +class PhoneViewFragment : BaseFragment() { + + companion object { + fun newInstance() = PhoneViewFragment().apply { + arguments = Bundle() + } + } + + private var flagViewVisibility: Boolean = true + + private var phoneViewCallback: PhoneViewCallback? = null + + override val viewModelClass = PhoneViewVM::class.java + + override fun hasToolbar() = false + override fun hasVersions() = true + override fun getToolbarId() = NO_TOOLBAR + + override fun observeLiveData() { + with(viewModel) { + countryLD.observe(this@PhoneViewFragment, Observer { countryAsset -> + countryAsset?.let { pvCodePicker?.setData(it) } + }) + validationLD.observe(this@PhoneViewFragment, Observer { response -> + response?.let { + tilPhone.error = it.errorMessage.takeIf { _ -> !it.isValid } + } + }) + } + } + + override fun getScreenTitle() = R.string.country + + override val layoutId = R.layout.fragment_phone_view + + override fun onAttach(context: Context) { + super.onAttach(context) + phoneViewCallback = bindInterfaceOrThrow(context) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initTestButtons() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + data?.takeIf { resultCode == Activity.RESULT_OK && requestCode == RequestCode.REQUEST_CHOOSE_COUNTRY() }?.let { + it.getParcelableExtra(ChooseCountryFragment.CHOOSE_COUNTRY_EXTRA)?.let { countryAsset -> + viewModel.countryLD.value = countryAsset + } + } + } + + override fun onDetach() { + phoneViewCallback = null + super.onDetach() + } + + private fun initTestButtons() { + tilPhone.editText?.addTextWatcher(BaseTextWatcher(actionOnTextChanged = { _, _, _, _ -> + tilPhone.error = null + })) + + tilCode.editText?.clickWithDebounce { + phoneViewCallback?.showChooseCountry() + } + + with(pvCodePicker) { + btFlagVisible.setOnClickListener { + flagViewVisibility = !flagViewVisibility + flagImageVisibility(flagViewVisibility) + } + btValidation.setOnClickListener { + val (code, phone) = getPhone() + viewModel.validatePhoneNumber(phone, code) + } + btIcon24.setOnClickListener { setIconSize(60) } + btIcon36.setOnClickListener { setIconSize(80) } + btIcon48.setOnClickListener { setIconSize(100) } + btIcon60.setOnClickListener { setIconSize(120) } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/PhoneViewVM.kt b/app/src/main/java/com/cleveroad/phone_example/PhoneViewVM.kt new file mode 100644 index 0000000..90971c4 --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/PhoneViewVM.kt @@ -0,0 +1,22 @@ +package com.cleveroad.phone_example + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import com.cleveroad.bootstrap.kotlin_core.ui.BaseLifecycleViewModel +import com.cleveroad.bootstrap.kotlin_phone_input.data.models.CountryAsset +import com.cleveroad.bootstrap.kotlin_validators.ValidationResponse + +class PhoneViewVM(application: Application) : BaseLifecycleViewModel(application) { + + private val phoneValidator = getPhoneValidator(application) + + val countryLD = MutableLiveData() + val validationLD = MutableLiveData() + + fun validatePhoneNumber(phoneNumber: String, countryRegion: String): Boolean = + phoneValidator.validate(phoneNumber, countryRegion).run { + validationLD.value = this + isValid + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/RequestCodes.kt b/app/src/main/java/com/cleveroad/phone_example/RequestCodes.kt new file mode 100644 index 0000000..f59bb17 --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/RequestCodes.kt @@ -0,0 +1,7 @@ +package com.cleveroad.phone_example + +enum class RequestCode { + REQUEST_CHOOSE_COUNTRY; + + operator fun invoke() = ordinal +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/phone_example/ValidatorFactory.kt b/app/src/main/java/com/cleveroad/phone_example/ValidatorFactory.kt new file mode 100644 index 0000000..1de28cb --- /dev/null +++ b/app/src/main/java/com/cleveroad/phone_example/ValidatorFactory.kt @@ -0,0 +1,13 @@ +package com.cleveroad.phone_example + +import android.content.Context +import com.cleveroad.bootstrap.kotlin_validators.PhoneValidatorImpl +import com.cleveroad.bootstrap.kotlin_validators.R + +fun getPhoneValidator(context: Context) = with(context) { + PhoneValidatorImpl + .builder(this) + .emptyErrorMessage(getString(R.string.phone_is_invalid)) + .invalidErrorMessage(getString(R.string.phone_is_invalid)) + .build() +} diff --git a/app/src/main/java/com/cleveroad/validator_example/EditText.kt b/app/src/main/java/com/cleveroad/validator_example/EditText.kt new file mode 100644 index 0000000..47afc8a --- /dev/null +++ b/app/src/main/java/com/cleveroad/validator_example/EditText.kt @@ -0,0 +1,17 @@ +package com.cleveroad.validator_example + +import android.widget.TextView + + +fun TextView.addSimpleTextChangedListener(listener: (text: String) -> Unit) = + object : SimpleTextWatcher(this) { + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + listener(s.toString()) + } + } + +fun TextView.getStringText() = this.text.toString() + +fun TextView.addFocusChangedListener(listener: (isHasFocus: Boolean) -> Unit) = this.setOnFocusChangeListener { _, hasFocus -> + listener(hasFocus) +} diff --git a/app/src/main/java/com/cleveroad/validator_example/SimpleTextWatcher.kt b/app/src/main/java/com/cleveroad/validator_example/SimpleTextWatcher.kt new file mode 100644 index 0000000..4795f58 --- /dev/null +++ b/app/src/main/java/com/cleveroad/validator_example/SimpleTextWatcher.kt @@ -0,0 +1,18 @@ +package com.cleveroad.validator_example + +import android.text.Editable +import android.text.TextWatcher +import android.widget.TextView + + +open class SimpleTextWatcher(textView: TextView) : TextWatcher { + init { + textView.addTextChangedListener(this) + } + + override fun afterTextChanged(s: Editable?) = Unit + + override fun beforeTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit +} \ No newline at end of file diff --git a/app/src/main/java/com/cleveroad/validator_example/ValidatorActivity.kt b/app/src/main/java/com/cleveroad/validator_example/ValidatorActivity.kt new file mode 100644 index 0000000..405db5b --- /dev/null +++ b/app/src/main/java/com/cleveroad/validator_example/ValidatorActivity.kt @@ -0,0 +1,99 @@ +package com.cleveroad.validator_example + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.cleveroad.bootstrap.kotlin.R +import com.cleveroad.bootstrap.kotlin_validators.* +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.textfield.TextInputLayout +import kotlinx.android.synthetic.main.activity_validator.* + + +class ValidatorActivity : AppCompatActivity() { + + private var nameValidator: Validator? = null + private var emailValidator: Validator? = null + private var phoneValidator: PhoneValidator? = null + private var passwordValidator: Validator? = null + private var matchPasswordValidator: MatchPasswordValidator? = null + private var nameWatcher: SimpleTextWatcher? = null + private var emailWatcher: SimpleTextWatcher? = null + private var countryRegion = "" + + companion object { + private const val EMPTY_MESSAGE = "" + + fun start(context: Context) = context.startActivity(Intent(context, ValidatorActivity::class.java)) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_validator) + initValidators() + + nameWatcher = etName.addSimpleTextChangedListener { text -> + nameValidator?.validate(text)?.let { showTextInputLayoutError(tilName, it) } + } + + emailWatcher = etEmail.addSimpleTextChangedListener { _ -> hideError(tilEmail) } + + etEmail.addFocusChangedListener { hasFocus -> + if (!hasFocus) { + emailValidator?.validate(etEmail.getStringText()) + ?.let { showTextInputLayoutError(tilEmail, it) } + } + } + + bValidateName.setOnClickListener { + nameValidator?.validate(etName.getStringText())?.let { showValidateResponse(it) } + } + bValidateEmail.setOnClickListener { + emailValidator?.validate(etEmail.getStringText())?.let { showValidateResponse(it) } + } + bValidatePhone.setOnClickListener { + phoneValidator?.validate(etPhone.getStringText(), countryRegion) + ?.let { showValidateResponse(it) } + } + bValidatePassword.setOnClickListener { + passwordValidator?.validate(etPassword.getStringText())?.let { showValidateResponse(it) } + } + bValidateMatchPassword.setOnClickListener { + matchPasswordValidator?.validate(etFirstPassword.getStringText(), etSecondPassword.getStringText()) + ?.let { showValidateResponse(it) } + } + } + + private fun showTextInputLayoutError(til: TextInputLayout, validate: ValidationResponse) { + til.error = if (validate.isValid) EMPTY_MESSAGE else validate.errorMessage + ?: getString(R.string.error) + } + + private fun hideError(til: TextInputLayout) { + til.error = EMPTY_MESSAGE + } + + override fun onStop() { + etName.removeTextChangedListener(nameWatcher) + etEmail.removeTextChangedListener(emailWatcher) + etEmail.onFocusChangeListener = null + super.onStop() + } + + private fun initValidators() { + nameValidator = ValidatorsFactory.getNameValidator(this) + emailValidator = ValidatorsFactory.getEmailValidator(this) + phoneValidator = ValidatorsFactory.getPhoneValidator(this) + passwordValidator = ValidatorsFactory.getPasswordValidator(this) + matchPasswordValidator = ValidatorsFactory.getMatchPasswordValidator(this) + } + + private fun showValidateResponse(validate: ValidationResponse) { + if (validate.isValid) showMessage() else showMessage(validate.errorMessage + ?: getString(R.string.error)) + } + + private fun showMessage(message: String = getString(R.string.correct)) = + Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show() +} diff --git a/app/src/main/res/drawable/background_social_button.xml b/app/src/main/res/drawable/background_social_button.xml new file mode 100644 index 0000000..1eaa89b --- /dev/null +++ b/app/src/main/res/drawable/background_social_button.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_auth.xml b/app/src/main/res/layout/activity_auth.xml new file mode 100644 index 0000000..bfd16ae --- /dev/null +++ b/app/src/main/res/layout/activity_auth.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_choose_country.xml b/app/src/main/res/layout/activity_choose_country.xml new file mode 100644 index 0000000..1db1653 --- /dev/null +++ b/app/src/main/res/layout/activity_choose_country.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_compress_image.xml b/app/src/main/res/layout/activity_compress_image.xml new file mode 100644 index 0000000..478ca10 --- /dev/null +++ b/app/src/main/res/layout/activity_compress_image.xml @@ -0,0 +1,18 @@ + + + +