├── .gitignore ├── .gitmodules ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── exozet │ │ │ └── android │ │ │ └── core │ │ │ └── demo │ │ │ └── ExampleInstrumentedTest.kt │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ ├── .gitignore │ │ │ └── my_text.json │ │ ├── java │ │ │ └── com │ │ │ │ └── exozet │ │ │ │ └── android │ │ │ │ └── core │ │ │ │ └── demo │ │ │ │ ├── App.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── WidgetSampleFragment.kt │ │ │ │ └── features │ │ │ │ └── realmLiveData │ │ │ │ ├── RepositoryViewModel.kt │ │ │ │ ├── StringPresenter.kt │ │ │ │ └── ViewModelRealmSampleFragment.kt │ │ └── res │ │ │ ├── anim │ │ │ ├── grow.xml │ │ │ └── layout_animation.xml │ │ │ ├── animator │ │ │ └── flip_animation.xml │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── ic_arrow_back_red_24dp.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── layer_launch_screen.xml │ │ │ ├── line.xml │ │ │ ├── line_drawable.xml │ │ │ ├── thumb.xml │ │ │ └── thumb_drawable.xml │ │ │ ├── font │ │ │ └── lato.ttf │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ ├── fragment_viewmodel_sample.xml │ │ │ ├── fragment_widget_sample.xml │ │ │ ├── layout_fab_sample.xml │ │ │ ├── layout_toolbar.xml │ │ │ └── recycler_view_item.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── raw │ │ │ └── my_raw.json │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── resource_test.xml │ │ │ └── strings.xml │ │ │ └── xml │ │ │ ├── lorem_ipsum.xml │ │ │ └── network_security_config.xml │ └── test │ │ ├── java │ │ └── com │ │ │ └── exozet │ │ │ └── android │ │ │ └── core │ │ │ ├── NumberTest.kt │ │ │ ├── WhenTests.kt │ │ │ ├── base │ │ │ ├── BaseTest.kt │ │ │ ├── SerializableTest.kt │ │ │ └── SerializableTests.kt │ │ │ ├── extensions │ │ │ └── StringExtensionsTest.kt │ │ │ ├── models │ │ │ └── ModelTest.kt │ │ │ └── sha256Test.kt │ │ └── res │ │ └── values │ │ └── strings.xml └── updateLocalization.sh ├── bintray.properties ├── build.gradle ├── core ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── debug │ └── AndroidManifest.xml │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── exozet │ │ └── android │ │ └── core │ │ ├── base │ │ ├── BaseActivity.kt │ │ ├── BaseApplication.kt │ │ ├── BaseFragment.kt │ │ ├── CompositeDisposableHolder.kt │ │ └── ViewHolder.kt │ │ ├── connectivity │ │ └── ConnectivityHelper.kt │ │ ├── extensions │ │ ├── Activity+Extensions.kt │ │ ├── Any+Extensions.kt │ │ ├── AppCompatActivity+Extensions.kt │ │ ├── Application+Extensions.kt │ │ ├── AudioManager+Extensions.kt │ │ ├── Bitmap+Extensions.kt │ │ ├── Boolean+Extensions.kt │ │ ├── BottomSheetBehaviour+Extensions.kt │ │ ├── Bundle+Extensions.kt │ │ ├── Button+Extensions.kt │ │ ├── ByteArray+Extensions.kt │ │ ├── Collection+Extensions.kt │ │ ├── Context+Extensions.kt │ │ ├── Date+Extensions.kt │ │ ├── Dialog+Extenions.kt │ │ ├── Disposable+Extenions.kt │ │ ├── EditText+Extensions.kt │ │ ├── Formatting+Extensions.kt │ │ ├── Fragment+Extensions.kt │ │ ├── InputStream+Extensions.kt │ │ ├── Int+Extensions.kt │ │ ├── Iterable+Extensions.kt │ │ ├── Koin+Extensions.kt │ │ ├── List+Extensions.kt │ │ ├── LocalDateTime+Extensions.kt │ │ ├── Long+Extensions.kt │ │ ├── Math+Extensions.kt │ │ ├── MutablieList+Extensions.kt │ │ ├── Notification+Extensions.kt │ │ ├── Number+Extensions.kt │ │ ├── Parcels+Extensions.kt │ │ ├── Realm+Extensions.kt │ │ ├── RealmCoroutines+Extensions.kt │ │ ├── RecyclerView+Extensions.kt │ │ ├── Resource+Extensions.kt │ │ ├── Retrofit+Extensions.kt │ │ ├── RxJava+Extensions.kt │ │ ├── Snackbar+Extensions.kt │ │ ├── String+Extensions.kt │ │ ├── SystemService+Extensions.kt │ │ ├── TextView+Extensions.kt │ │ ├── URL+Extensions.kt │ │ ├── Uri+Extensions.kt │ │ ├── View+Extensions.kt │ │ ├── ViewModel+Extensions.kt │ │ ├── ViewPager+Extensions.kt │ │ └── WebView+Extensions.kt │ │ ├── gson │ │ ├── Exclude.kt │ │ ├── GsonExclusionStrategy.kt │ │ └── GsonProvider.kt │ │ ├── input │ │ ├── AmazonFireTvRemoteControlListener.kt │ │ ├── FlingGestureHandler.java │ │ ├── KeyEventDelegate.java │ │ ├── KeyListener.java │ │ └── RemoteControlListener.java │ │ ├── interfaces │ │ ├── BackPress.kt │ │ ├── ChainableCommand.java │ │ ├── Command.java │ │ ├── DispatchTouchEvent.kt │ │ ├── DispatchTouchEventHandler.kt │ │ ├── LayoutProvider.kt │ │ ├── Repository.kt │ │ ├── StorageProvider.java │ │ └── annotations │ │ │ ├── HapticFeedback.kt │ │ │ ├── ScreenOrientation.kt │ │ │ ├── Transit.kt │ │ │ └── Visibility.kt │ │ ├── io │ │ └── Cache.java │ │ ├── locale │ │ ├── CountryCode.kt │ │ └── LocaleHelper.java │ │ ├── markdown │ │ ├── MarkdownFragment.kt │ │ └── RawOutputFragment.kt │ │ ├── math │ │ ├── Line.java │ │ ├── RandomXS128.java │ │ └── Vector2.java │ │ ├── misc │ │ ├── AutoClearedValue.kt │ │ ├── Bundler.java │ │ ├── DefaultUserAgent.kt │ │ ├── FakeDataGenerator.kt │ │ ├── SynchronizedValue.java │ │ ├── UIDGenerator.kt │ │ └── WeakReferenceDelegate.kt │ │ ├── models │ │ └── LoadingInfo.kt │ │ ├── realm │ │ ├── RealmDao.kt │ │ ├── RealmModelLiveData.kt │ │ ├── RealmRepository.kt │ │ └── RealmResultsLiveData.kt │ │ ├── services │ │ ├── call │ │ │ └── PhoneCallReceiver.java │ │ ├── connectivity │ │ │ ├── LiveNetworkMonitor.kt │ │ │ ├── NetworkChangeReceiver.kt │ │ │ └── NoNetworkException.kt │ │ ├── crypto │ │ │ └── Blowfish.kt │ │ ├── network │ │ │ ├── GoogleApi.kt │ │ │ ├── NullableConverterFactory.java │ │ │ ├── RetrofitException.kt │ │ │ ├── RxErrorHandlingCallAdapterFactory.kt │ │ │ ├── SslUtils.kt │ │ │ ├── fcm │ │ │ │ ├── FcmMessage.kt │ │ │ │ └── NotificationContent.kt │ │ │ └── interceptors │ │ │ │ ├── AuthorizationInterceptor.kt │ │ │ │ ├── ContentTypeInterceptor.kt │ │ │ │ ├── EmptyInterceptor.kt │ │ │ │ ├── ErrorInterceptor.kt │ │ │ │ ├── InterceptorFactory.kt │ │ │ │ ├── LoadingInterceptor.kt │ │ │ │ └── UrlDecodedInterceptor.kt │ │ ├── notifications │ │ │ ├── FBMessagingService.kt │ │ │ ├── FBNotificationJobService.kt │ │ │ ├── GcmSender.kt │ │ │ └── PushNotificationPublisher.kt │ │ └── sms │ │ │ └── SmsReceiver.kt │ │ ├── storage │ │ ├── FirebaseDatabaseHelper.kt │ │ ├── FragmentArgumentDelegate.kt │ │ ├── SecurePreferencesLongString.kt │ │ ├── SecureStorageDelegate.kt │ │ └── SharedPreferenceDelegate.kt │ │ ├── time │ │ └── TimestampConvert.java │ │ ├── ui │ │ ├── Makeup.java │ │ ├── custom │ │ │ ├── AppBarLayoutOverScrollViewBehavior.kt │ │ │ ├── BottomNavigationBehavior.kt │ │ │ ├── CustomBottomSheetBehavior.kt │ │ │ ├── CustomCardView.kt │ │ │ ├── DisableableAppBarLayoutBehavior.kt │ │ │ ├── IPAddressEditText.java │ │ │ ├── ItemSnapHelper.kt │ │ │ ├── LineDrawingDecoration.kt │ │ │ ├── LinesView.kt │ │ │ ├── MaskableFrameLayout.kt │ │ │ ├── NonSwipeableViewPager.kt │ │ │ ├── ScrollListener.kt │ │ │ ├── SwipeDistanceView.kt │ │ │ └── ZoomageView.kt │ │ ├── itemDecorator │ │ │ ├── DividerItemDecoration.java │ │ │ ├── GridEntrust.java │ │ │ ├── GridItemDecoration.kt │ │ │ ├── GridSpacingItemDecoration.java │ │ │ ├── LinearEntrust.java │ │ │ ├── SpacesItemDecoration.java │ │ │ └── SpacesItemDecorationEntrust.java │ │ └── nullobjects │ │ │ ├── SimpleAnimationListener.kt │ │ │ ├── SimpleAnimatorListener.kt │ │ │ ├── SimpleBaseFragmentAdapter.kt │ │ │ ├── SimpleMotionListener.kt │ │ │ ├── SimpleOnCheckedChangeListener.kt │ │ │ ├── SimpleOnItemSelectedListener.kt │ │ │ ├── SimpleOnPageChangeListener.kt │ │ │ ├── SimpleTabSelectionListener.kt │ │ │ └── SimpleTextWatcher.kt │ │ ├── uid │ │ └── PrimaryKeyGenerator.kt │ │ └── utils │ │ ├── ActivityExtensions.java │ │ ├── BitmapExtensions.java │ │ ├── ByteExtensions.java │ │ ├── CalendarExtensions.java │ │ ├── ClassExtensions.java │ │ ├── DateExtensions.java │ │ ├── DeviceExtensions.java │ │ ├── DrawableExtensions.java │ │ ├── EncryptionExtensions.java │ │ ├── FontExtensions.java │ │ ├── FragmentExtensions.java │ │ ├── JUnitExtensions.java │ │ ├── KeyGuardExtensions.java │ │ ├── MathExtensions.java │ │ ├── MathHelper.java │ │ ├── ShellExtensions.kt │ │ ├── ThreadExtensions.java │ │ ├── VibrationExtensions.java │ │ └── ViewExtensions.java │ └── res │ ├── anim │ ├── custom_enter_from_bottom.xml │ ├── custom_enter_from_top.xml │ ├── custom_exit_to_bottom.xml │ ├── custom_exit_to_top.xml │ ├── enter_from_bottom.xml │ ├── enter_from_left.xml │ ├── enter_from_right.xml │ ├── exit_to_bottom.xml │ ├── exit_to_left.xml │ ├── exit_to_right.xml │ ├── fade_in.xml │ ├── fade_out.xml │ ├── grow.xml │ ├── pop_in.xml │ ├── pop_out.xml │ ├── shake.xml │ ├── shrink.xml │ ├── side_in.xml │ ├── side_out.xml │ ├── slide_enter.xml │ ├── slide_enter_right.xml │ ├── slide_exit.xml │ ├── slide_exit_left.xml │ ├── slide_in.xml │ ├── slide_in_top.xml │ ├── slide_out.xml │ ├── slide_out_bottom.xml │ ├── slide_pop_enter.xml │ └── slide_pop_exit.xml │ ├── animator │ ├── alpha_in.xml │ ├── alpha_out.xml │ ├── fade_in.xml │ ├── fade_out.xml │ ├── flip_animation.xml │ ├── raise.xml │ └── shrink_scale_animation.xml │ ├── drawable │ ├── ic_launcher.xml │ └── ic_share.xml │ ├── layout │ ├── activity_main.xml │ ├── fragment_markdown.xml │ └── fragment_raw_output.xml │ ├── transition │ └── move.xml │ ├── values-ldltr │ └── ltr-config.xml │ ├── values-ldrtl │ └── rtl-config.xml │ ├── values-sw600dp │ └── tablet-config.xml │ ├── values-v21 │ ├── styles.xml │ └── themes.xml │ ├── values │ ├── attr.xml │ ├── colors.xml │ ├── config.xml │ ├── debug.xml │ ├── dimens.xml │ ├── fonts.xml │ ├── integers.xml │ ├── ltr-config.xml │ ├── strings.xml │ ├── styles.xml │ ├── themes.xml │ └── transition_names.xml │ └── xml │ └── backup.xml ├── debug.jks ├── documentation └── AmazonFireTvRemoteControlListener.jpg ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── release.jks └── settings.gradle /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Android-Dependencies"] 2 | path = Android-Dependencies 3 | url = https://github.com/exozet/Android-Dependencies.git 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Exozet GmbH 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "761920839110", 4 | "firebase_url": "https://androidcore-exozet.firebaseio.com", 5 | "project_id": "androidcore-exozet", 6 | "storage_bucket": "androidcore-exozet.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:761920839110:android:588901dc9a9996c3", 12 | "android_client_info": { 13 | "package_name": "com.exozet.android.core.demo" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "761920839110-7kp2qohhetr2emeq7t8frmfr9mfpl36v.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.exozet.android.core.demo", 22 | "certificate_hash": "ae98a892e282182458bb7d2d3d366d13381f0801" 23 | } 24 | }, 25 | { 26 | "client_id": "761920839110-1ev37noehfuu5qkqjf2qraefli0nvgci.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyAkiWv6PXIVlYmrf2BF6Y7TyO-S15SKXm0" 33 | } 34 | ], 35 | "services": { 36 | "analytics_service": { 37 | "status": 1 38 | }, 39 | "appinvite_service": { 40 | "status": 2, 41 | "other_platform_oauth_client": [ 42 | { 43 | "client_id": "761920839110-1ev37noehfuu5qkqjf2qraefli0nvgci.apps.googleusercontent.com", 44 | "client_type": 3 45 | } 46 | ] 47 | }, 48 | "ads_service": { 49 | "status": 2 50 | } 51 | } 52 | } 53 | ], 54 | "configuration_version": "1" 55 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/exozet/android/core/demo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.demo 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | import org.junit.runner.RunWith 6 | 7 | /** 8 | * Instrumented test, which will execute on an Android device. 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | @RunWith(AndroidJUnit4::class) 13 | class ExampleInstrumentedTest { 14 | @Test 15 | fun useAppContext() { 16 | // Context of the app under test. 17 | val appContext = InstrumentationRegistry.getTargetContext() 18 | assertEquals("com.exozet.android.core", appContext.packageName) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/assets/.gitignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | README.md 3 | RELEASE.md -------------------------------------------------------------------------------- /app/src/main/assets/my_text.json: -------------------------------------------------------------------------------- 1 | { 2 | "lorem" : "ipsum" 3 | } -------------------------------------------------------------------------------- /app/src/main/java/com/exozet/android/core/demo/App.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.demo 2 | 3 | import com.exozet.android.core.base.BaseApplication 4 | 5 | /** 6 | * Created by [Jan Rabe](https://about.me/janrabe). 7 | */ 8 | 9 | class App : BaseApplication() { 10 | 11 | override fun onCreate() { 12 | super.onCreate() 13 | initRealm() 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/exozet/android/core/demo/WidgetSampleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.demo 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.exozet.android.core.base.BaseFragment 7 | import com.exozet.android.core.extensions.indeterminateDrawableColor 8 | import com.exozet.android.core.extensions.onClick 9 | import kotlinx.android.synthetic.main.fragment_widget_sample.* 10 | import kotlinx.android.synthetic.main.layout_toolbar.* 11 | import net.kibotu.logger.snack 12 | 13 | /** 14 | * Created by armando.shkurti on 04.04.18. 15 | */ 16 | class WidgetSampleFragment : BaseFragment() { 17 | 18 | override val layout = R.layout.fragment_widget_sample 19 | 20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 21 | super.onViewCreated(view, savedInstanceState) 22 | 23 | toolbar.title = "Sample" 24 | (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) 25 | 26 | progressBar.indeterminateDrawableColor(R.color.white) 27 | 28 | customToolbarBack.onClick { 29 | snack("Back") 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/exozet/android/core/demo/features/realmLiveData/StringPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.demo.features.realmLiveData 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import com.exozet.android.core.demo.R 5 | import kotlinx.android.synthetic.main.recycler_view_item.view.* 6 | import net.kibotu.android.recyclerviewpresenter.Adapter 7 | import net.kibotu.android.recyclerviewpresenter.Presenter 8 | import net.kibotu.android.recyclerviewpresenter.PresenterModel 9 | import net.kibotu.logger.Logger.logv 10 | 11 | class StringPresenter : Presenter() { 12 | 13 | override val layout: Int = R.layout.recycler_view_item 14 | 15 | override fun bindViewHolder(viewHolder: RecyclerView.ViewHolder, item: PresenterModel, position: Int, payloads: MutableList?, adapter: Adapter) { 16 | logv { "bindViewHolder: ${item.model}" } 17 | 18 | with(viewHolder.itemView) { 19 | itemTextView.text = item.model.value 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/exozet/android/core/demo/features/realmLiveData/ViewModelRealmSampleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.demo.features.realmLiveData 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.lifecycle.Observer 6 | import androidx.lifecycle.ViewModelProvider 7 | import com.exozet.android.core.base.BaseFragment 8 | import com.exozet.android.core.demo.R 9 | import com.exozet.android.core.extensions.onClick 10 | import com.exozet.android.core.uid.PrimaryKeyGenerator 11 | import kotlinx.android.synthetic.main.fragment_viewmodel_sample.* 12 | import net.kibotu.android.recyclerviewpresenter.PresenterAdapter 13 | import net.kibotu.android.recyclerviewpresenter.PresenterModel 14 | 15 | 16 | class ViewModelRealmSampleFragment : BaseFragment() { 17 | 18 | override val layout: Int = R.layout.fragment_viewmodel_sample 19 | 20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 21 | super.onViewCreated(view, savedInstanceState) 22 | 23 | val adapter = PresenterAdapter() 24 | adapter.registerPresenter(StringPresenter()) 25 | list.adapter = adapter 26 | 27 | val vm = ViewModelProvider(this).get(RepositoryViewModel::class.java) 28 | 29 | vm.strings.observe(this, Observer { 30 | 31 | val primaryKeyGenerator = PrimaryKeyGenerator() 32 | 33 | adapter.submitList(it.map { PresenterModel(it, R.layout.recycler_view_item, uuid = primaryKeyGenerator.newUID().toString()) }) 34 | 35 | list.smoothScrollToPosition(it.lastIndex.coerceAtLeast(0)) 36 | }) 37 | 38 | add.onClick { vm.addItem() } 39 | 40 | remove.onClick { vm.deleteAll() } 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/grow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/anim/layout_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/animator/flip_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 19 | 20 | 26 | 27 | 31 | 32 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | 20 | 23 | 26 | 27 | 28 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_red_24dp.xml: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/layer_launch_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/line.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/line_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/thumb_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/font/lato.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/font/lato.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_fab_sample.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recycler_view_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/raw/my_raw.json: -------------------------------------------------------------------------------- 1 | { 2 | "lorem" : "ipsum" 3 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidCore 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/lorem_ipsum.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Brian Cantoni 5 | Generate random Lorem Ipsum text from lipsum.com. Parameters: amount=number of things to fetch; what: paras, words, bytes, or lists; start: set to "no" to not start all strings with Lorem Ipsum. 6 | http://www.lipsum.com/ 7 | select * from {table} where amount='5' and what='paras'; 8 | 9 | 10 | 28 | 29 |
-------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/test/java/com/exozet/android/core/NumberTest.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core 2 | 3 | import com.exozet.android.core.base.BaseTest 4 | import com.exozet.android.core.extensions.percentToValueOfRange 5 | import com.exozet.android.core.extensions.valueToPercentOfRange 6 | import com.google.common.truth.Truth 7 | import org.junit.Test 8 | 9 | class NumberTest : BaseTest() { 10 | 11 | @Test 12 | fun percentTest() { 13 | 14 | val min = 0f 15 | val max = 100f 16 | 17 | (0..100).forEach { 18 | 19 | val expected = (it - min) / (max - min) 20 | Truth.assertThat(it.toFloat().valueToPercentOfRange(min, max)).isEqualTo(expected) 21 | } 22 | 23 | Truth.assertThat(0f.valueToPercentOfRange(-100f, 100f)).isEqualTo(0.5f) 24 | Truth.assertThat(0f.valueToPercentOfRange(100f, -100f)).isEqualTo(0.5f) 25 | } 26 | 27 | @Test 28 | fun percentValueTest() { 29 | 30 | val min = 0f 31 | val max = 100f 32 | 33 | (0..100).forEach { 34 | 35 | val expected = min + it * (max - min) 36 | Truth.assertThat(it.toFloat().percentToValueOfRange(min, max)).isEqualTo(expected) 37 | } 38 | 39 | Truth.assertThat(0.5f.percentToValueOfRange(-100f, 100f)).isEqualTo(0f) 40 | Truth.assertThat(0.5f.percentToValueOfRange(100f, -100f)).isEqualTo(0f) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/test/java/com/exozet/android/core/WhenTests.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core 2 | 3 | import com.exozet.android.core.base.BaseTest 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | /** 8 | * Created by [Jan Rabe](https://about.me/janrabe). 9 | */ 10 | 11 | class WhenTests : BaseTest() { 12 | 13 | @Test 14 | fun whenTestTrueTrue() { 15 | 16 | val a = true 17 | val b = true 18 | 19 | when { 20 | a and b -> Assert.assertTrue(true) 21 | a -> Assert.fail() 22 | else -> Assert.fail() 23 | } 24 | 25 | when { 26 | a -> Assert.assertTrue(true) 27 | a and b -> Assert.fail() 28 | else -> Assert.fail() 29 | } 30 | } 31 | 32 | @Test 33 | fun whenTestTrueFalse() { 34 | 35 | val a = true 36 | val b = false 37 | 38 | when { 39 | a and b -> Assert.fail() 40 | a -> Assert.assertTrue(true) 41 | else -> Assert.fail() 42 | } 43 | } 44 | 45 | @Test 46 | fun whenTestFalseFalse() { 47 | 48 | val a = false 49 | val b = false 50 | 51 | when { 52 | a and b -> Assert.fail() 53 | a -> Assert.fail() 54 | else -> Assert.assertTrue(true) 55 | } 56 | } 57 | 58 | @Test 59 | fun whenTestFalseTrue() { 60 | 61 | val a = false 62 | val b = true 63 | 64 | when { 65 | a and b -> Assert.fail() 66 | a -> Assert.fail() 67 | else -> Assert.assertTrue(true) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/test/java/com/exozet/android/core/base/SerializableTest.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.base 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Created by [Jan Rabe](https://about.me/janrabe). 7 | */ 8 | 9 | interface SerializableTest { 10 | 11 | @Test 12 | fun serialize() 13 | 14 | @Test 15 | fun deserialize() 16 | } -------------------------------------------------------------------------------- /app/src/test/java/com/exozet/android/core/base/SerializableTests.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.base 2 | 3 | import com.exozet.android.core.gson.gson 4 | import com.exozet.android.core.gson.toJsonPrettyPrinting 5 | import net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | /** 10 | * Created by [Jan Rabe](https://about.me/janrabe). 11 | */ 12 | 13 | abstract class SerializableTests(var klass: Class<*>) : BaseTest() { 14 | 15 | open val expectedJson: String = "" 16 | 17 | @Test 18 | open fun serialize() { 19 | val actual = gson.fromJson(expectedJson.trimMargin(), klass) 20 | Assert.assertNotNull(actual) 21 | } 22 | 23 | @Test 24 | open fun deserialize() { 25 | val actual = gson.fromJson(expectedJson.trimMargin(), klass) 26 | Assert.assertNotNull(actual) 27 | 28 | val actualJson = actual.toJsonPrettyPrinting() 29 | assertJsonEquals(expectedJson.trimMargin(), actualJson) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/test/java/com/exozet/android/core/models/ModelTest.kt: -------------------------------------------------------------------------------- 1 | package de.charite.balsam.models 2 | 3 | import com.exozet.android.core.base.SerializableTests 4 | 5 | /** 6 | * Created by [Jan Rabe](https://about.me/janrabe). 7 | */ 8 | 9 | class ModelTest : SerializableTests(TestModel::class.java) { 10 | 11 | override val expectedJson = """ 12 | { 13 | "message": "test" 14 | } 15 | """ 16 | } 17 | 18 | data class TestModel(var message: String? = null) -------------------------------------------------------------------------------- /app/src/test/java/com/exozet/android/core/sha256Test.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core 2 | 3 | import com.exozet.android.core.base.BaseTest 4 | import com.exozet.android.core.extensions.sha256_HMAC 5 | import com.google.common.truth.Truth 6 | import org.junit.Test 7 | 8 | 9 | class sha : BaseTest() { 10 | /** 11 | * https://tools.ietf.org/html/rfc7636#appendix-A 12 | */ 13 | @Test 14 | fun sha256_HMACTest() { 15 | Truth.assertThat("any carnal pleasure.".sha256_HMAC()).isEqualTo("klV7mnDnMorgaiIdbLmVSjsQDWP3UpClQSSnaI2nOXE=") 16 | } 17 | 18 | @Test 19 | fun retrievalTokenEncryptionTest() { 20 | val actual = "any carnal pleasure.".sha256_HMAC() 21 | 22 | val expected = "klV7mnDnMorgaiIdbLmVSjsQDWP3UpClQSSnaI2nOXE=" 23 | Truth.assertThat(actual).isEqualTo(expected) 24 | 25 | val expected2 = "klV7mnDnMorgaiIdbLmVSjsQDWP3UpClQSSnaI2nOXE" 26 | val removedCharacters = actual.replace("+", "-") 27 | .replace("/", "_") 28 | .replace("=", "") 29 | Truth.assertThat(removedCharacters).isEqualTo(expected2) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/test/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/updateLocalization.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | authKey="" 3 | valuesDir="src/main/res/values" 4 | languages="de" 5 | defaultLanguage="de" 6 | 7 | # Main string files 8 | for language in $languages 9 | do 10 | if [ "$language" == $defaultLanguage ]; then 11 | saveDir="$valuesDir" 12 | else 13 | saveDir="$valuesDir-$language" 14 | fi 15 | 16 | mkdir -p $saveDir 17 | 18 | curl "https://localise.biz/api/export/locale/$language.xml?key=$authKey&format=android&fallback=$defaultLanguage" > "$saveDir/strings.xml" 19 | done -------------------------------------------------------------------------------- /bintray.properties: -------------------------------------------------------------------------------- 1 | bintray.user=exozet 2 | bintray.apikey= 3 | bintray.organization=exozetag 4 | bintray.gpg.password= 5 | binrtray.group=com.exozet 6 | binrtray.repo=maven 7 | binrtray.name=AndroidCore 8 | bintray.licenses=MIT 9 | bintray.vcsUrl=https://github.com/exozet/AndroidCore.git 10 | bintray.websiteUrl=https://github.com/exozet/AndroidCore 11 | bintray.version.desc="Collection of re-usable android functions." -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | 3 | apply from: "${project.rootDir}/Android-Dependencies/dependencies.gradle" 4 | 5 | repositories { 6 | // https://storage-download.googleapis.com/maven-central/index.html 7 | maven { url 'https://maven-central-eu.storage-download.googleapis.com/repos/central/data/' } 8 | google() 9 | maven { url 'https://plugins.gradle.org/m2/' } 10 | maven { url 'https://jitpack.io' } 11 | maven { url 'https://maven.fabric.io/public' } 12 | 13 | // realm beta 14 | maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } 15 | } 16 | 17 | dependencies { 18 | classpath plugin.gradleBuildTools 19 | classpath plugin.navigationSafeArgs 20 | classpath plugin.kotlinGradle 21 | classpath plugin.kotlinApt 22 | // classpath plugin.realm 23 | classpath plugin.realmBeta 24 | classpath plugin.kotlinDokka 25 | classpath plugin.fabric 26 | classpath plugin.imgOptimizer 27 | classpath plugin.hockeyApp 28 | classpath plugin.gms 29 | classpath plugin.fabric 30 | classpath pluginLibrary.mavenGradle 31 | classpath plugin.dexcountGradle 32 | 33 | classpath pluginLibrary.mavenGradle 34 | classpath pluginLibrary.gradleBintrayPlugin 35 | } 36 | } 37 | 38 | allprojects { 39 | 40 | apply from: "${project.rootDir}/Android-Dependencies/utils.gradle" 41 | 42 | repositories { 43 | // https://storage-download.googleapis.com/maven-central/index.html 44 | maven { url 'https://maven-central-eu.storage-download.googleapis.com/repos/central/data/' } 45 | google() 46 | maven { url 'https://plugins.gradle.org/m2/' } 47 | maven { url 'https://jitpack.io' } 48 | maven { url 'https://dl.bintray.com/exozetag/maven' } 49 | maven { url "https://oss.sonatype.org/content/repositories/snapshots" } 50 | 51 | // realm beta 52 | maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } 53 | } 54 | } 55 | 56 | task clean(type: Delete) { 57 | delete rootProject.buildDir 58 | } -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/gradle.properties: -------------------------------------------------------------------------------- 1 | #Project Object Model https://maven.apache.org/pom.html#What_is_the_POM 2 | POM_NAME=AndroidCore 3 | POM_DESCRIPTION=Common used utilities 4 | # https://bintray.com/exozetag/maven/AndroidCore 5 | POM_BINTRAY_NAME=AndroidCore 6 | POM_ARTIFACT_ID=core 7 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep com.exozet.android.core.** { *; } -------------------------------------------------------------------------------- /core/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 22 | 23 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.base 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.LayoutRes 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.exozet.android.core.R 7 | import com.exozet.android.core.extensions.currentFragment 8 | import com.exozet.android.core.extensions.printBackStack 9 | import com.exozet.android.core.interfaces.BackPress 10 | import com.google.android.gms.common.ConnectionResult.* 11 | import com.google.android.gms.common.GoogleApiAvailability 12 | 13 | /** 14 | * Created by armando.shkurti on 14/12/17. 15 | */ 16 | @Deprecated("just demo, don't use") 17 | abstract class BaseActivity : AppCompatActivity() { 18 | 19 | @LayoutRes 20 | var activityLayoutId = R.layout.activity_main 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | 25 | setContentView(activityLayoutId) 26 | 27 | checkGooglePlayServices() 28 | } 29 | 30 | override fun onBackPressed() { 31 | 32 | // hide keyboard 33 | // DeviceExtensions.hideKeyboard() 34 | 35 | // let fragments handle back press 36 | val fragment = currentFragment() 37 | if (fragment is BackPress && fragment.consumeBackPress()) 38 | return 39 | 40 | // pop back stack 41 | if (supportFragmentManager.backStackEntryCount > 0) { 42 | supportFragmentManager.popBackStack() 43 | printBackStack() 44 | return 45 | } 46 | 47 | super.onBackPressed() 48 | } 49 | 50 | private fun checkGooglePlayServices() { 51 | when (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this)) { 52 | SERVICE_MISSING -> GoogleApiAvailability.getInstance().getErrorDialog(this, SERVICE_MISSING, 0).show() 53 | SERVICE_VERSION_UPDATE_REQUIRED -> GoogleApiAvailability.getInstance().getErrorDialog(this, SERVICE_VERSION_UPDATE_REQUIRED, 0).show() 54 | SERVICE_DISABLED -> GoogleApiAvailability.getInstance().getErrorDialog(this, SERVICE_DISABLED, 0).show() 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/base/CompositeDisposableHolder.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.base 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | 5 | interface CompositeDisposableHolder { 6 | 7 | var subscription: CompositeDisposable 8 | 9 | fun disposeCompositeDisposable() 10 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/base/ViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.base 2 | 3 | import android.view.ViewGroup 4 | import androidx.annotation.CallSuper 5 | import androidx.annotation.LayoutRes 6 | import io.reactivex.disposables.Disposable 7 | 8 | abstract class ViewHolder(var itemView: ViewGroup?) : Disposable { 9 | 10 | @get:LayoutRes 11 | protected abstract val layout: Int 12 | 13 | protected var disposed = false 14 | 15 | override fun isDisposed() = disposed 16 | 17 | @CallSuper 18 | override fun dispose() { 19 | itemView = null 20 | disposed = true 21 | } 22 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Any+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | 4 | inline fun tryCatch(function: () -> T) = try { 5 | function() 6 | } catch (e: Exception) { 7 | null 8 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/AudioManager+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("AudioManagerExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import android.media.AudioManager 6 | import android.media.AudioManager.* 7 | import android.os.Build.VERSION.SDK_INT 8 | import android.os.Build.VERSION_CODES.M 9 | import androidx.core.math.MathUtils.clamp 10 | 11 | /** 12 | * Created by [Jan Rabe](https://about.me/janrabe). 13 | */ 14 | 15 | private var _isMuted: Boolean = false 16 | 17 | var AudioManager.isMuted: Boolean 18 | get() = _isMuted 19 | private set(value) { 20 | _isMuted = value 21 | } 22 | 23 | @Suppress("DEPRECATION") 24 | fun AudioManager.mute(mute: Boolean = true) { 25 | isMuted = mute 26 | if (SDK_INT >= M) { 27 | val flag = if (mute) ADJUST_MUTE else ADJUST_UNMUTE 28 | adjustStreamVolume(STREAM_NOTIFICATION, flag, 0) 29 | adjustStreamVolume(STREAM_ALARM, flag, 0) 30 | adjustStreamVolume(STREAM_MUSIC, flag, 0) 31 | adjustStreamVolume(STREAM_RING, flag, 0) 32 | adjustStreamVolume(STREAM_SYSTEM, flag, 0) 33 | } else { 34 | setStreamMute(STREAM_NOTIFICATION, mute) 35 | setStreamMute(STREAM_ALARM, mute) 36 | setStreamMute(STREAM_MUSIC, mute) 37 | setStreamMute(STREAM_RING, mute) 38 | setStreamMute(STREAM_SYSTEM, mute) 39 | } 40 | } 41 | 42 | @Suppress("DEPRECATION") 43 | fun AudioManager.unmute() { 44 | mute(false) 45 | } 46 | 47 | fun AudioManager.decreaseVolume() { 48 | changeVolume(-10) 49 | } 50 | 51 | fun AudioManager.increaseVolume() { 52 | changeVolume(10) 53 | } 54 | 55 | fun AudioManager.toggleMute() { 56 | mute(!isMuted) 57 | } 58 | 59 | fun AudioManager.changeVolume(dt: Int) { 60 | val streamMaxVolume = audioManager.getStreamVolume(STREAM_MUSIC) 61 | val volume = clamp(streamMaxVolume + dt, 0, streamMaxVolume) 62 | audioManager.setStreamVolume(STREAM_MUSIC, volume, 0) 63 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Bitmap+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("BitmapExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import android.graphics.Bitmap 6 | import net.kibotu.logger.Logger 7 | 8 | /** 9 | * Created by [Jan Rabe](https://about.me/janrabe). 10 | */ 11 | 12 | fun Bitmap?.freeBitmap() { 13 | if (this == null || isRecycled) { 14 | return 15 | } 16 | 17 | try { 18 | recycle() 19 | } catch (e: Exception) { 20 | Logger.e(e) 21 | } 22 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Boolean+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("BooleanExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | /** 6 | * Created by [Jan Rabe](https://about.me/janrabe). 7 | */ 8 | 9 | inline fun Boolean.whether(yes: () -> T, no: () -> T): T = if (this) yes() else no() 10 | 11 | inline fun Boolean.either(t: T): Pair = Pair(this, t) 12 | 13 | inline infix fun Pair.or(t: T): T = if (first) second else t 14 | 15 | fun Boolean.onTrue(function: () -> Unit) { 16 | if (this) { 17 | function() 18 | } else { 19 | 20 | } 21 | } 22 | 23 | fun Boolean.onFalse(function: () -> Unit) { 24 | if (this) { 25 | } else { 26 | function() 27 | } 28 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/BottomSheetBehaviour+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("BottomSheetBehaviourExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import android.view.View 6 | import com.google.android.material.bottomsheet.BottomSheetBehavior 7 | 8 | inline var BottomSheetBehavior.isExpanded: Boolean 9 | get() = state == BottomSheetBehavior.STATE_EXPANDED 10 | set(expand) { 11 | state = if (expand) BottomSheetBehavior.STATE_EXPANDED else BottomSheetBehavior.STATE_COLLAPSED 12 | } 13 | 14 | inline var BottomSheetBehavior.isCollapsed: Boolean 15 | get() = state == BottomSheetBehavior.STATE_COLLAPSED 16 | set(collapse) { 17 | state = if (collapse) BottomSheetBehavior.STATE_COLLAPSED else BottomSheetBehavior.STATE_EXPANDED 18 | } 19 | 20 | inline var BottomSheetBehavior.isExpandedHalf: Boolean 21 | get() = state == BottomSheetBehavior.STATE_HALF_EXPANDED 22 | set(expandHalf) { 23 | state = if (expandHalf) BottomSheetBehavior.STATE_HALF_EXPANDED else BottomSheetBehavior.STATE_EXPANDED 24 | } 25 | 26 | inline var BottomSheetBehavior.isHidden: Boolean 27 | get() = state == BottomSheetBehavior.STATE_HIDDEN 28 | set(hide) { 29 | isHideable = true 30 | state = if (hide) BottomSheetBehavior.STATE_HIDDEN else BottomSheetBehavior.STATE_COLLAPSED 31 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Bundle+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("BundleExtensions") 2 | 3 | 4 | package com.exozet.android.core.extensions 5 | 6 | import android.os.Bundle 7 | import net.kibotu.logger.Logger.logv 8 | import net.kibotu.logger.TAG 9 | 10 | 11 | fun Bundle.printBundle() { 12 | for (key in keySet()) { 13 | val value = get(key) 14 | logv { "${value?.TAG} $key: $value" } 15 | } 16 | } 17 | 18 | val Bundle.string 19 | get() = with(StringBuilder()) { 20 | append("{") 21 | for (key in keySet()) { 22 | val value = get(key) 23 | append(key).append(":").append(value) 24 | } 25 | append("}") 26 | }.toString() -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Button+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ButtonExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import android.R 6 | import android.content.res.ColorStateList 7 | import android.graphics.drawable.Drawable 8 | import android.graphics.drawable.StateListDrawable 9 | import android.view.View 10 | import android.widget.CompoundButton 11 | import androidx.annotation.ColorRes 12 | import androidx.appcompat.widget.AppCompatButton 13 | import net.kibotu.resourceextension.resColor 14 | 15 | /** 16 | * Created by [Jan Rabe](https://about.me/janrabe). 17 | */ 18 | 19 | 20 | fun CompoundButton.setCheckedWithoutAnimation(checked: Boolean) { 21 | val beforeVisibility = visibility 22 | visibility = View.INVISIBLE 23 | isChecked = checked 24 | visibility = beforeVisibility 25 | } 26 | 27 | fun AppCompatButton.iconWith(defaultIcon: Drawable, selectedIcon: Drawable): StateListDrawable = StateListDrawable().apply { 28 | val iconSelected = selectedIcon 29 | iconSelected.setBounds(0, 0, 21, 21) 30 | val defaultIcon = defaultIcon 31 | defaultIcon.setBounds(0, 0, 21, 21) 32 | 33 | addState(intArrayOf(R.attr.state_selected), iconSelected) 34 | addState(intArrayOf(-R.attr.state_selected), defaultIcon) 35 | addState(intArrayOf(), defaultIcon) 36 | } 37 | 38 | fun AppCompatButton.textColorsWith(@ColorRes color: Int, @ColorRes selectedColor: Int): ColorStateList { 39 | return ColorStateList( 40 | arrayOf( 41 | intArrayOf(R.attr.state_selected), 42 | intArrayOf(-R.attr.state_selected), 43 | intArrayOf() 44 | ), 45 | intArrayOf( 46 | selectedColor.resColor, 47 | color.resColor, 48 | color.resColor 49 | ) 50 | ) 51 | } 52 | 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/ByteArray+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ByteArrayExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import net.kibotu.logger.Logger 6 | import java.io.File 7 | import java.io.FileOutputStream 8 | 9 | /** 10 | * Created by [Jan Rabe](https://about.me/janrabe). 11 | */ 12 | 13 | fun ByteArray.saveAt(pathName: String): Boolean = try { 14 | FileOutputStream(File(pathName)).use { it.write(this) } 15 | true 16 | } catch (e: Exception) { 17 | Logger.e(e) 18 | false 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Collection+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("CollectionExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import java.util.* 6 | import kotlin.random.Random 7 | 8 | /** 9 | * Created by [Jan Rabe](https://about.me/janrabe). 10 | */ 11 | 12 | 13 | fun Collection.isLast(position: Int) = position == size - 1 14 | 15 | 16 | inline fun T.addToList(list: MutableList): T = this.apply { 17 | list.add(this) 18 | } 19 | 20 | fun Collection<*>?.indexOutOfBounds(index: Int): Boolean { 21 | return this == null || index < 0 || index > this.size - 1 22 | } 23 | 24 | fun java.util.Collection.removeInRange(position: Int, count: Int) { 25 | this.removeAll(drop(position).take(count)) 26 | } 27 | 28 | fun java.util.List.removeInRange(position: Int, count: Int) { 29 | this.removeAll(drop(position).take(count)) 30 | } 31 | 32 | fun java.util.ArrayList.removeInRange(position: Int, count: Int) { 33 | this.removeAll(drop(position).take(count)) 34 | } 35 | 36 | fun Collection.intersect(other: MutableCollection): Collection { 37 | val result = ArrayList() 38 | for (t in this) { 39 | if (other.remove(t)) result.add(t) 40 | } 41 | return result 42 | } 43 | 44 | fun Collection<*>?.isEmpty() = this == null || this.isEmpty() 45 | 46 | /** 47 | * Returns a random element from this collection using the specified source of randomness. 48 | * 49 | * @throws NoSuchElementException if this collection is empty. 50 | */ 51 | @SinceKotlin("1.3") 52 | fun Collection.random(random: Int): List { 53 | if (isEmpty()) 54 | throw NoSuchElementException("Collection is empty.") 55 | return (0 until random).map { elementAt(Random.nextInt(size)) } 56 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Date+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("DateExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import android.annotation.SuppressLint 6 | import android.os.Build 7 | import android.provider.Settings 8 | import android.text.TextUtils 9 | import net.kibotu.ContextHelper 10 | import java.text.SimpleDateFormat 11 | import java.util.* 12 | 13 | /** 14 | * Created by [Jan Rabe](https://about.me/janrabe). 15 | */ 16 | 17 | /** 18 | * https://stackoverflow.com/a/43550290 19 | */ 20 | @SuppressLint("SimpleDateFormat") 21 | fun Date?.localize(): String { 22 | if (this == null) 23 | return "" 24 | 25 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { 26 | val pattern = android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "EEEEMMMMdyyyy") //order on the String doesn't matter 27 | val formatter = SimpleDateFormat(pattern, Locale.getDefault()) 28 | 29 | return formatter.format(this) 30 | } 31 | 32 | val format = Settings.System.getString(ContextHelper.getApplication()!!.contentResolver, Settings.System.DATE_FORMAT) 33 | val formatter = if (TextUtils.isEmpty(format)) 34 | android.text.format.DateFormat.getMediumDateFormat(ContextHelper.getApplication()) 35 | else 36 | SimpleDateFormat(format) 37 | 38 | return formatter.format(this) 39 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Dialog+Extenions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import android.app.Dialog 4 | import android.os.Build 5 | import android.view.Window 6 | import android.view.WindowManager 7 | 8 | 9 | fun Dialog.addFullScreenFlags(): Dialog { 10 | window?.addFullScreenFlags() 11 | return this 12 | } 13 | 14 | fun Window.addFullScreenFlags() { 15 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) 16 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) 17 | requestFeature(Window.FEATURE_NO_TITLE) 18 | setBackgroundDrawableResource(android.R.color.transparent) 19 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { 20 | setClipToOutline(false) 21 | } 22 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Disposable+Extenions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import android.view.View 4 | import io.reactivex.disposables.Disposable 5 | 6 | 7 | fun Disposable.clearOnDetach(view: View) = view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { 8 | 9 | override fun onViewAttachedToWindow(v: View?) { 10 | } 11 | 12 | override fun onViewDetachedFromWindow(v: View?) { 13 | if (!isDisposed) 14 | dispose() 15 | 16 | view.removeOnAttachStateChangeListener(this) 17 | } 18 | }) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/EditText+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import android.text.TextWatcher 4 | import android.widget.EditText 5 | import com.exozet.android.core.ui.nullobjects.SimpleTextWatcher 6 | 7 | 8 | fun EditText.onTextChanged(block: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit): TextWatcher { 9 | 10 | val listener = object : SimpleTextWatcher() { 11 | 12 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { 13 | block(s, start, before, count) 14 | } 15 | } 16 | addTextChangedListener(listener) 17 | return listener 18 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Formatting+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("FormattingExtensions") 2 | 3 | 4 | package com.exozet.android.core.extensions 5 | 6 | /** 7 | * Created by [Jan Rabe](https://about.me/janrabe). 8 | */ 9 | 10 | const val BYTES_TO_KB: Long = 1024 11 | const val BYTES_TO_MB = BYTES_TO_KB * 1024 12 | const val BYTES_TO_GB = BYTES_TO_MB * 1024 13 | const val BYTES_TO_TB = BYTES_TO_GB * 1024 14 | 15 | /** 16 | * alternative to Formatter.formatFileSize which doesn't show bytes and rounds to int 17 | */ 18 | fun Long.formatBytes(): String { 19 | if (this <= 0) 20 | return "0 bytes" 21 | 22 | return if (this / BYTES_TO_TB > 0) 23 | String.format("%.2f TB", this / BYTES_TO_TB.toFloat()) 24 | else if (this / BYTES_TO_GB > 0) 25 | String.format("%.2f GB", this / BYTES_TO_GB.toFloat()) 26 | else if (this / BYTES_TO_MB > 0) 27 | String.format("%.2f MB", this / BYTES_TO_MB.toFloat()) 28 | else if (this / BYTES_TO_KB > 0) String.format("%.2f KB", this / BYTES_TO_KB.toFloat()) else "$this bytes" 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Fragment+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("FragmentExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import androidx.annotation.IdRes 6 | import androidx.fragment.app.Fragment 7 | import com.exozet.android.core.R 8 | import net.kibotu.ContextHelper.getAppCompatActivity 9 | import net.kibotu.logger.Logger 10 | 11 | 12 | /** 13 | * Created by [Jan Rabe](https://about.me/janrabe). 14 | */ 15 | 16 | @IdRes 17 | var fragmentContainerId: Int = R.id.fragment_container 18 | 19 | fun currentFragment(@IdRes container: Int = fragmentContainerId): Fragment? { 20 | return getAppCompatActivity()!!.supportFragmentManager.findFragmentById(container) 21 | } 22 | 23 | fun printBackStack() { 24 | val fm = getAppCompatActivity()!!.supportFragmentManager 25 | Logger.v("Fragment", "Current BackStack: " + fm.backStackEntryCount) 26 | for (entry in 0 until fm.backStackEntryCount) { 27 | val stackEntry = fm.getBackStackEntryAt(entry) 28 | Logger.v("Fragment", "[" + stackEntry.id + "] " + stackEntry.name) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/InputStream+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("InputStreamExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import java.io.InputStream 6 | import java.nio.charset.Charset 7 | 8 | /** 9 | * Created by [Jan Rabe](https://about.me/janrabe). 10 | */ 11 | 12 | fun InputStream.readTextAndClose(charset: Charset = Charsets.UTF_8): String = bufferedReader(charset).use { it.readText() } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Int+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("IntExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import kotlin.random.Random 6 | 7 | 8 | fun Int.sleep() = this.toLong().sleep() 9 | 10 | fun Long.sleep() = Thread.sleep(this) 11 | 12 | fun Int.randomStrings(): String { 13 | val randomStringBuilder = StringBuilder() 14 | var tempChar: Char 15 | for (i in 0 until this) { 16 | tempChar = (Random.nextInt(96) + 32).toChar() 17 | randomStringBuilder.append(tempChar) 18 | } 19 | return randomStringBuilder.toString() 20 | } 21 | 22 | inline fun Int.repeat(factory: (index: Int) -> T) = arrayListOf().apply { for (i in 0..this@repeat) add(factory(i)) } 23 | 24 | inline fun Int.clampToListSize(list: List): Int = clamp(0, list.lastIndex) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Iterable+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import java.text.Collator 4 | import java.util.* 5 | import kotlin.math.absoluteValue 6 | 7 | 8 | inline fun Iterable.mostFrequent(crossinline keySelector: (T) -> K) = 9 | groupingBy(keySelector).eachCount().maxBy { it.value }?.key 10 | 11 | fun Iterable.closestValue(value: Int) = minBy { value.minus(it).absoluteValue } 12 | 13 | inline fun Iterable.sortedWithLocale(locale: Locale = Locale.GERMAN, crossinline selector: (T) -> Comparable<*>?): List { 14 | 15 | // ensure locale 16 | val comparator: Comparator = compareBy(Collator.getInstance(locale), selector) 17 | 18 | if (this is Collection) { 19 | if (size <= 1) return this.toList() 20 | @Suppress("UNCHECKED_CAST") 21 | return (toTypedArray() as Array).apply { sortWith(comparator) }.asList() 22 | } 23 | return toMutableList().apply { sortWith(comparator) } 24 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Koin+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("KoinExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import org.koin.core.context.GlobalContext 6 | import org.koin.core.parameter.ParametersDefinition 7 | import org.koin.core.qualifier.Qualifier 8 | 9 | 10 | /** 11 | * inject lazily given dependency for Android koincomponent 12 | * @param qualifier - bean qualifier / optional 13 | * @param scope 14 | * @param parameters - injection parameters 15 | */ 16 | inline fun Any.inject( 17 | qualifier: Qualifier? = null, 18 | noinline parameters: ParametersDefinition? = null 19 | ) = lazy { GlobalContext.get().koin.get(qualifier, parameters) } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/List+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | 4 | inline fun List.circularIndex(index: Int): Int = 5 | if (index < 0) (index % size + size) % size 6 | else index % size 7 | 8 | inline fun List.circularAt(index: Int): T = this[circularIndex(index)] -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/LocalDateTime+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import org.joda.time.DateTimeZone 4 | import org.joda.time.Duration 5 | import org.joda.time.LocalDateTime 6 | import org.joda.time.format.DateTimeFormat 7 | import org.joda.time.format.DateTimeFormatter 8 | 9 | 10 | fun LocalDateTime.durationUntil(end: LocalDateTime) = Duration(toDateTime(DateTimeZone.UTC), end.toDateTime(DateTimeZone.UTC)) 11 | 12 | val String.iso8601Date: LocalDateTime 13 | get() = LocalDateTime.parse(this, iso8601Formatter) 14 | 15 | val LocalDateTime.iso8601Date: String 16 | get() = toString(iso8601Formatter) 17 | 18 | /** 19 | * 2013-01-09T19:32:49.103+05:30 20 | */ 21 | val iso8601Formatter: DateTimeFormatter by lazy { DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ") } 22 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Long+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import java.util.* 4 | import kotlin.math.abs 5 | 6 | 7 | /** 8 | * https://stackoverflow.com/a/3758880/1006741 9 | */ 10 | fun Long.humanReadableByteCountBinary(): String { 11 | val b = when (this) { 12 | Long.MIN_VALUE -> Long.MAX_VALUE 13 | else -> abs(this) 14 | } 15 | return when { 16 | b < 1024L -> "$this B" 17 | b <= 0xfffccccccccccccL shr 40 -> "%.1f KiB".format(Locale.UK, this / 1024.0) 18 | b <= 0xfffccccccccccccL shr 30 -> "%.1f MiB".format(Locale.UK, this / 1048576.0) 19 | b <= 0xfffccccccccccccL shr 20 -> "%.1f GiB".format(Locale.UK, this / 1.073741824E9) 20 | b <= 0xfffccccccccccccL shr 10 -> "%.1f TiB".format(Locale.UK, this / 1.099511627776E12) 21 | b <= 0xfffccccccccccccL -> "%.1f PiB".format(Locale.UK, (this shr 10) / 1.099511627776E12) 22 | else -> "%.1f EiB".format(Locale.UK, (this shr 20) / 1.099511627776E12) 23 | } 24 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/MutablieList+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | 4 | inline fun MutableList.addNotNull(item: T?) = item?.let { add(it) } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Number+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import kotlin.contracts.ExperimentalContracts 4 | import kotlin.contracts.contract 5 | 6 | 7 | /** 8 | * Returns `true` if this not null or zero. 9 | */ 10 | @UseExperimental(ExperimentalContracts::class) 11 | inline fun T?.onNotNullOrZero(block: T.() -> Unit): Boolean { 12 | contract { 13 | returns(true) implies (this@onNotNullOrZero != null) 14 | } 15 | 16 | return if (this != null && this.isNotZero()) { 17 | block(this) 18 | true 19 | } else { 20 | false 21 | } 22 | } 23 | 24 | inline fun T.format(digits: Int) = "%.${digits}f".format(this) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Parcels+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ParcelsExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import android.os.Bundle 6 | import android.os.Parcelable 7 | import org.parceler.Parcels 8 | 9 | /** 10 | * Created by [Jan Rabe](https://about.me/janrabe). 11 | */ 12 | 13 | inline fun T.wrap(): Parcelable = Parcels.wrap(this) 14 | 15 | inline fun Parcelable?.unwrap(): T? = Parcels.unwrap(this) 16 | 17 | inline fun Bundle.unwrap(key: String?): T? = getParcelable(key).unwrap() 18 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Realm+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import com.exozet.android.core.realm.RealmDao 4 | import com.exozet.android.core.realm.RealmModelLiveData 5 | import com.exozet.android.core.realm.RealmResultsLiveData 6 | import io.realm.RealmList 7 | import io.realm.RealmModel 8 | import io.realm.RealmObject 9 | import io.realm.RealmResults 10 | 11 | inline fun RealmResults.asLiveData(valueOnActive: Boolean = false) = RealmResultsLiveData(this, valueOnActive) 12 | 13 | inline fun T.asLiveData(valueOnActive: Boolean = false) = RealmModelLiveData(this, valueOnActive) 14 | 15 | inline fun T.detachRealm(): T = realm?.copyFromRealm(this) ?: this 16 | 17 | /** 18 | * https://github.com/vicpinm/Kotlin-Realm-Extensions/blob/master/library-base/src/main/java/com/vicpin/krealmextensions/RealmExtensions.kt#L275 19 | */ 20 | inline fun T.update(crossinline block: T.(t: T) -> Unit) { 21 | realm.executeTransaction { 22 | block(this) 23 | } 24 | } 25 | 26 | /** 27 | * Deletes all [T]. 28 | */ 29 | inline fun RealmDao.deleteAllSync() = realm.executeTransaction { realm -> 30 | realm.where(T::class.java) 31 | .findAll() 32 | .deleteAllFromRealm() 33 | } 34 | 35 | /** 36 | * Inserts or updates all [T]. 37 | */ 38 | inline fun RealmDao.insertOrUpdateSync(t: T) = realm.executeTransaction { realm -> realm.insertOrUpdate(t) } 39 | 40 | /** 41 | * Inserts or updates all [T]. 42 | */ 43 | inline fun RealmDao.insertOrUpdateSync(t: List) = realm.executeTransaction { realm -> realm.insertOrUpdate(t) } 44 | 45 | /** 46 | * Deletes all [T]. 47 | */ 48 | inline fun RealmDao.allSync(): RealmResults = realm.where(T::class.java).findAll() 49 | 50 | inline fun RealmDao.isEmpty() = realm.where(T::class.java).findFirst() == null 51 | 52 | inline fun List.toRealmList(): RealmList? { 53 | val list = RealmList() 54 | list.addAll(this) 55 | return list 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/RecyclerView+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("RecyclerViewExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import androidx.recyclerview.widget.RecyclerView 6 | import androidx.recyclerview.widget.SimpleItemAnimator 7 | 8 | /** 9 | * Created by [Jan Rabe](https://about.me/janrabe). 10 | */ 11 | 12 | val RecyclerView.Adapter.isEmpty: Boolean where VH : RecyclerView.ViewHolder 13 | get() = itemCount == 0 14 | 15 | 16 | var RecyclerView.supportsChangeAnimations: Boolean 17 | get() = (itemAnimator as SimpleItemAnimator).supportsChangeAnimations 18 | set(value) { 19 | (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = value 20 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Retrofit+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import net.kibotu.logger.Logger.loge 4 | import okhttp3.Request 5 | import okhttp3.Response 6 | 7 | inline fun retrofit2.Response.log() { 8 | val message = "${code()} ${message()} ${errorBody()}" 9 | loge { message } 10 | } 11 | 12 | fun Response.setCustomHeader(key: String, value: String) = request.setAuthHeader(key, value) 13 | 14 | fun Request.setCustomHeader(key: String, value: String) = newBuilder() 15 | .removeHeader(key) 16 | .addHeader(key, value) 17 | .build() 18 | 19 | fun Response.setAuthHeader(authorization: String = "JWTAuthorization", token: String) = request.setAuthHeader(authorization, token) 20 | 21 | fun Request.setAuthHeader(authorization: String = "JWTAuthorization", token: String) = newBuilder() 22 | .removeHeader(authorization) 23 | .addHeader(authorization, "Bearer $token") 24 | .build() 25 | 26 | fun Response.setCacheKey(cacheKey: String = "JWTCacheKey", token: String) = request.setAuthHeader(cacheKey, token) 27 | 28 | fun Request.setCacheKey(cacheKey: String = "JWTCacheKey", token: String) = newBuilder() 29 | .removeHeader(cacheKey) 30 | .addHeader(cacheKey, token) 31 | .build() -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/RxJava+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("RxJavaExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import io.reactivex.Flowable 6 | import io.reactivex.android.schedulers.AndroidSchedulers 7 | import io.reactivex.disposables.Disposable 8 | import io.reactivex.schedulers.Schedulers 9 | 10 | /** 11 | * Created by armando.shkurti on 31.01.18. 12 | */ 13 | fun Flowable.applySchedulersIO(): Flowable { 14 | return subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) 15 | } 16 | 17 | fun Flowable.applySchedulersComputation(): Flowable { 18 | return subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()) 19 | } 20 | 21 | fun Flowable.subscribeUIToIO(onNext: (T) -> Unit, onError: (Throwable) -> Unit): Disposable { 22 | return applySchedulersIO().subscribe({ next -> 23 | onNext(next) 24 | }, { error -> 25 | onError(error) 26 | }) 27 | } 28 | 29 | fun Flowable.subscribeUIToComputation(onNext: (T) -> Unit, onError: (Throwable) -> Unit): Disposable { 30 | return applySchedulersComputation().subscribe({ next -> 31 | onNext(next) 32 | }, { error -> 33 | onError(error) 34 | }) 35 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Snackbar+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("SnackbarExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import android.widget.TextView 6 | import com.google.android.material.snackbar.Snackbar 7 | 8 | fun Snackbar.withTextColor(color: Int): Snackbar { 9 | view.findViewById(com.google.android.material.R.id.snackbar_text).setTextColor(color) 10 | return this 11 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/URL+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("URLExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import java.net.HttpURLConnection 6 | import java.net.URL 7 | import java.net.URLConnection 8 | 9 | /** 10 | * Created by [Jan Rabe](https://about.me/janrabe). 11 | */ 12 | 13 | inline fun URL.use(block: (URLConnection) -> R): R { 14 | var connection: URLConnection? = null 15 | try { 16 | connection = openConnection() 17 | return block(connection) 18 | } catch (e: Exception) { 19 | throw e 20 | } finally { 21 | (connection as? HttpURLConnection)?.disconnect() 22 | } 23 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/Uri+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | import android.net.Uri 4 | import android.os.Environment 5 | import androidx.browser.customtabs.CustomTabsIntent 6 | import com.github.florent37.application.provider.ActivityProvider 7 | import com.github.florent37.application.provider.application 8 | import de.cketti.shareintentbuilder.ShareIntentBuilder 9 | import net.kibotu.ContextHelper 10 | import java.io.File 11 | 12 | /** 13 | * Created by [Jan Rabe](https://about.me/janrabe). 14 | */ 15 | 16 | fun String.parseAssetFile(): Uri = Uri.parse("file:///android_asset/$this") 17 | 18 | fun String.parseInternalStorageFile(): Uri = Uri.parse("${ContextHelper.getApplication()!!.filesDir.absolutePath}/$this") 19 | 20 | fun String.parseExternalStorageFile(): Uri = Uri.parse("${Environment.getExternalStorageDirectory()}/$this") 21 | 22 | fun String.parseFile(): Uri = Uri.fromFile(File(this)) 23 | 24 | val Uri.fileExists: Boolean 25 | get() = File(toString()).exists() 26 | 27 | fun Uri.share() = ShareIntentBuilder.from(application!!) 28 | .text(this.toString()) 29 | .share() 30 | 31 | fun Uri.openChromeTab() = CustomTabsIntent.Builder() 32 | .build() 33 | .launchUrl(ActivityProvider.currentActivity!!, this) 34 | 35 | fun Uri.openDialPad() = toString().replace("tel:", "").openDialPad() 36 | 37 | fun Uri.sendEmail() = 38 | sendEmail(toString().replace("mailto:", "")) 39 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/ViewModel+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ViewModelExtensions") 2 | 3 | package com.exozet.android.core.extensions 4 | 5 | import androidx.annotation.MainThread 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.ViewModelProvider 8 | import androidx.lifecycle.ViewModelStoreOwner 9 | import com.github.florent37.application.provider.ActivityProvider 10 | 11 | 12 | /** 13 | * Try using `by viewModels` if possible. 14 | */ 15 | @MainThread 16 | inline fun viewModel(viewModelStoreOwner: ViewModelStoreOwner = ActivityProvider.currentActivity as ViewModelStoreOwner) = lazy { ViewModelProvider(viewModelStoreOwner).get(VM::class.java) } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/ViewPager+Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ViewPagerExtensions") 2 | 3 | 4 | package com.exozet.android.core.extensions 5 | 6 | import androidx.viewpager.widget.ViewPager 7 | 8 | fun ViewPager.scrollRight(pages: Int = 1) { 9 | setCurrentItem(currentItem.plus(pages).clamp(0, (adapter?.count ?: 0) - 1), true) 10 | } 11 | 12 | fun ViewPager.scrollLeft(pages: Int = 1) { 13 | setCurrentItem(currentItem.minus(pages).clamp(0, (adapter?.count ?: 0) - 1), true) 14 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/extensions/WebView+Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.extensions 2 | 3 | 4 | import android.os.Build 5 | import android.webkit.WebView 6 | import androidx.annotation.RequiresApi 7 | 8 | 9 | @RequiresApi(Build.VERSION_CODES.KITKAT) 10 | fun WebView.evaluateJavascriptCommandFetch(command: String, onSuccess: (String?) -> Unit) = evaluateJavascript("(function() { return $command; })();") { onSuccess(it) } 11 | 12 | @RequiresApi(Build.VERSION_CODES.KITKAT) 13 | fun WebView.evaluateJavascriptCommand(command: String, onSuccess: ((String?) -> Unit)? = null) = evaluateJavascript(""" (function() { $command })(); """.trimMargin()) { onSuccess?.invoke(it) } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/gson/Exclude.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.gson 2 | 3 | 4 | @Retention(AnnotationRetention.RUNTIME) 5 | @Target(AnnotationTarget.FIELD) 6 | annotation class Exclude -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/gson/GsonExclusionStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.gson 2 | 3 | import com.google.gson.ExclusionStrategy 4 | import com.google.gson.FieldAttributes 5 | 6 | /** 7 | * https://www.baeldung.com/gson-exclude-fields-serialization 8 | */ 9 | class GsonExclusionStrategy : ExclusionStrategy { 10 | 11 | override fun shouldSkipClass(clazz: Class<*>): Boolean = false 12 | 13 | override fun shouldSkipField(field: FieldAttributes): Boolean = field.getAnnotation(Exclude::class.java) != null 14 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/gson/GsonProvider.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("GsonProvider") 2 | 3 | package com.exozet.android.core.gson 4 | 5 | import com.exozet.android.core.time.TimestampConvert 6 | import com.google.gson.Gson 7 | import com.google.gson.reflect.TypeToken 8 | 9 | /** 10 | * Created by [Jan Rabe](https://about.me/janrabe). 11 | */ 12 | 13 | 14 | val gsonWithDate by lazy { 15 | with(GsonExclusionStrategy()) { 16 | com.google.gson.GsonBuilder() 17 | .addSerializationExclusionStrategy(this) 18 | .addDeserializationExclusionStrategy(this) 19 | .disableHtmlEscaping() 20 | .setDateFormat(TimestampConvert.iso8601Format()) 21 | .create() 22 | } 23 | } 24 | 25 | val gson by lazy { 26 | with(GsonExclusionStrategy()) { 27 | com.google.gson.GsonBuilder() 28 | .addSerializationExclusionStrategy(this) 29 | .addDeserializationExclusionStrategy(this) 30 | .disableHtmlEscaping() 31 | .create() 32 | } 33 | } 34 | 35 | val gsonPrettyPrinting by lazy { 36 | with(GsonExclusionStrategy()) { 37 | com.google.gson.GsonBuilder() 38 | .addSerializationExclusionStrategy(this) 39 | .addDeserializationExclusionStrategy(this) 40 | .disableHtmlEscaping() 41 | .setPrettyPrinting() 42 | .create() 43 | } 44 | } 45 | 46 | inline fun T.toJson(): String = gson.toJson(this) 47 | 48 | inline fun T.toJsonPrettyPrinting(): String = gsonPrettyPrinting.toJson(this) 49 | 50 | inline fun String.fromJson(): T = gson.fromJson(this) 51 | 52 | inline fun Gson.fromJson(json: String): T = this.fromJson(json, object : TypeToken() {}.type) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/input/KeyListener.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.input; 2 | 3 | import android.view.KeyEvent; 4 | 5 | public interface KeyListener { 6 | 7 | boolean onKeyUp(final int keyCode, final KeyEvent event); 8 | 9 | boolean onKeyDown(final int keyCode, final KeyEvent event); 10 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/BackPress.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces 2 | 3 | interface BackPress { 4 | 5 | fun consumeBackPress(): Boolean 6 | } 7 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/ChainableCommand.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | /** 7 | * Created by Jan Rabe on 25/09/15. 8 | */ 9 | public interface ChainableCommand { 10 | 11 | T execute(@NonNull final T t); 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/Command.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | /** 7 | * Created by Jan Rabe on 25/09/15. 8 | */ 9 | public interface Command { 10 | 11 | void execute(@NonNull final T t); 12 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/DispatchTouchEvent.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces 2 | 3 | import android.view.MotionEvent 4 | 5 | /** 6 | * Created by Jan Rabe on 28/09/15. 7 | */ 8 | @Deprecated("use DispatchTouchEventHandler", ReplaceWith("DispatchTouchEventHandler")) 9 | interface DispatchTouchEvent { 10 | 11 | fun dispatchTouchEvent(event: MotionEvent): Boolean 12 | } 13 | 14 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/DispatchTouchEventHandler.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces 2 | 3 | import android.view.MotionEvent 4 | import android.view.View 5 | 6 | interface DispatchTouchEventHandler { 7 | 8 | val viewsHideKeyboardOnFocusLoss: Array? 9 | 10 | fun dispatchTouchEvent(event: MotionEvent): Boolean 11 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/LayoutProvider.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces 2 | 3 | 4 | import androidx.annotation.LayoutRes 5 | 6 | /** 7 | * Created by jan.rabe on 02/02/16. 8 | */ 9 | interface LayoutProvider { 10 | 11 | @get:LayoutRes 12 | val layout: Int 13 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/Repository.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces 2 | 3 | import androidx.lifecycle.LiveData 4 | 5 | /** 6 | * Answers where to get data. 7 | */ 8 | interface Repository { 9 | 10 | /** 11 | * Gets all [T]. 12 | */ 13 | fun all(): LiveData> 14 | 15 | /** 16 | * Gets [T] by id. 17 | */ 18 | fun getById(id: String): LiveData 19 | 20 | /** 21 | * Deletes [T]. 22 | */ 23 | fun delete(item: T) 24 | 25 | /** 26 | * Updates and inserts [T]. 27 | */ 28 | fun insertOrUpdate(item: T) 29 | 30 | /** 31 | * Updates and inserts list of [T]. 32 | */ 33 | fun insertOrUpdate(items: List) 34 | 35 | /** 36 | * Loads and updates. 37 | */ 38 | suspend fun loadAndUpdate() 39 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/StorageProvider.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces; 2 | 3 | import java.util.List; 4 | 5 | public interface StorageProvider { 6 | 7 | boolean create(T t); 8 | 9 | List read(); 10 | 11 | boolean delete(T t); 12 | 13 | void clear(); 14 | 15 | boolean contains(T t); 16 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/annotations/HapticFeedback.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces.annotations 2 | 3 | import android.annotation.TargetApi 4 | import android.os.Build 5 | import android.view.HapticFeedbackConstants 6 | import androidx.annotation.IntDef 7 | 8 | @TargetApi(Build.VERSION_CODES.O_MR1) 9 | @IntDef( 10 | value = [ 11 | HapticFeedbackConstants.CLOCK_TICK, 12 | HapticFeedbackConstants.CONTEXT_CLICK, 13 | HapticFeedbackConstants.KEYBOARD_PRESS, // same as HapticFeedbackConstants.KEYBOARD_TAP 14 | HapticFeedbackConstants.KEYBOARD_RELEASE, 15 | HapticFeedbackConstants.LONG_PRESS, 16 | HapticFeedbackConstants.TEXT_HANDLE_MOVE, 17 | HapticFeedbackConstants.VIRTUAL_KEY, 18 | HapticFeedbackConstants.VIRTUAL_KEY_RELEASE] 19 | ) 20 | @Retention(AnnotationRetention.SOURCE) 21 | annotation class HapticFeedback -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/annotations/ScreenOrientation.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces.annotations 2 | 3 | import android.content.pm.ActivityInfo.* 4 | import androidx.annotation.IntDef 5 | 6 | 7 | @IntDef( 8 | value = [ 9 | SCREEN_ORIENTATION_UNSPECIFIED, 10 | SCREEN_ORIENTATION_LANDSCAPE, 11 | SCREEN_ORIENTATION_PORTRAIT, 12 | SCREEN_ORIENTATION_USER, 13 | SCREEN_ORIENTATION_BEHIND, 14 | SCREEN_ORIENTATION_SENSOR, 15 | SCREEN_ORIENTATION_NOSENSOR, 16 | SCREEN_ORIENTATION_SENSOR_LANDSCAPE, 17 | SCREEN_ORIENTATION_SENSOR_PORTRAIT, 18 | SCREEN_ORIENTATION_REVERSE_LANDSCAPE, 19 | SCREEN_ORIENTATION_REVERSE_PORTRAIT, 20 | SCREEN_ORIENTATION_FULL_SENSOR, 21 | SCREEN_ORIENTATION_USER_LANDSCAPE, 22 | SCREEN_ORIENTATION_USER_PORTRAIT, 23 | SCREEN_ORIENTATION_FULL_USER, 24 | SCREEN_ORIENTATION_LOCKED] 25 | ) 26 | @Retention(AnnotationRetention.SOURCE) 27 | annotation class ScreenOrientation -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/annotations/Transit.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces.annotations 2 | 3 | 4 | import androidx.annotation.IntDef 5 | 6 | 7 | import androidx.fragment.app.FragmentTransaction.* 8 | 9 | @IntDef(TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE) 10 | @Retention(AnnotationRetention.SOURCE) 11 | annotation class Transit -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/interfaces/annotations/Visibility.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.interfaces.annotations 2 | 3 | 4 | import android.view.View.* 5 | import androidx.annotation.IntDef 6 | 7 | @IntDef(VISIBLE, INVISIBLE, GONE) 8 | @Retention(AnnotationRetention.SOURCE) 9 | annotation class Visibility -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/io/Cache.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.io; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import java.io.File; 9 | 10 | /** 11 | * Created by Jan Rabe on 18/06/15. 12 | */ 13 | public class Cache { 14 | 15 | public static void deleteCache(@NonNull final Context context) { 16 | try { 17 | final File dir = context.getCacheDir(); 18 | if (dir != null && dir.isDirectory()) { 19 | deleteDir(dir); 20 | } 21 | } catch (@NonNull final Exception e) { 22 | } 23 | } 24 | 25 | public static boolean deleteDir(@Nullable final File dir) { 26 | if (dir != null && dir.isDirectory()) { 27 | final String[] children = dir.list(); 28 | for (int i = 0; i < children.length; i++) { 29 | final boolean success = deleteDir(new File(dir, children[i])); 30 | if (!success) { 31 | return false; 32 | } 33 | } 34 | } 35 | return dir.delete(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/locale/CountryCode.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.locale 2 | 3 | /** 4 | * Created by jan.rabe on 14/04/15. 5 | */ 6 | /// 7 | /// @see http://countrycode.org/ 8 | /// 9 | enum class CountryCode { 10 | UNKNOWN, 11 | ALB, 12 | AND, 13 | ARE, 14 | ARG, 15 | AUS, 16 | AUT, 17 | BEL, 18 | BGR, 19 | BHR, 20 | BHS, 21 | BIH, 22 | BLR, 23 | BRA, 24 | BRN, 25 | BWA, 26 | CAN, 27 | CHE, 28 | CHL, 29 | CHN, 30 | CYM, 31 | CZE, 32 | DEU, 33 | DNK, 34 | EGY, 35 | ESP, 36 | EST, 37 | FIN, 38 | FRA, 39 | GBR, 40 | GIB, 41 | GLP, 42 | GRC, 43 | GUF, 44 | GUY, 45 | HKG, 46 | HRV, 47 | HUN, 48 | IDN, 49 | IND, 50 | IRL, 51 | ISL, 52 | ITA, 53 | JEY, 54 | JOR, 55 | KWT, 56 | KOS, 57 | LBN, 58 | LIE, 59 | LTU, 60 | LUX, 61 | LVA, 62 | MCO, 63 | MDA, 64 | MEX, 65 | MKD, 66 | MLT, 67 | MNE, 68 | MTQ, 69 | MYS, 70 | NLD, 71 | NOR, 72 | NZL, 73 | OMN, 74 | POL, 75 | PRI, 76 | PRT, 77 | QAT, 78 | REU, 79 | ROU, 80 | RUS, 81 | SAU, 82 | SGP, 83 | SMR, 84 | SRB, 85 | SVK, 86 | SVN, 87 | SWE, 88 | SWZ, 89 | THA, 90 | TUR, 91 | TWN, 92 | UKR, 93 | USA, 94 | VAT, 95 | VEN, 96 | VIR, 97 | ZAF 98 | } 99 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/markdown/MarkdownFragment.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.markdown 2 | 3 | import android.os.Bundle 4 | import android.text.TextUtils.isEmpty 5 | import android.view.View 6 | import com.exozet.android.core.R 7 | import com.exozet.android.core.base.BaseFragment 8 | import com.exozet.android.core.storage.bundle 9 | import kotlinx.android.synthetic.main.fragment_markdown.* 10 | 11 | class MarkdownFragment : BaseFragment() { 12 | 13 | override val layout = R.layout.fragment_markdown 14 | 15 | var filename by bundle() 16 | 17 | var url by bundle() 18 | 19 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 20 | super.onViewCreated(view, savedInstanceState) 21 | 22 | when { 23 | !isEmpty(filename) -> markdown_view.loadMarkdownFromAssets(filename) 24 | !isEmpty(url) -> markdown_view.loadUrl(url) 25 | } 26 | 27 | markdown_view.isOpenUrlInBrowser = true 28 | } 29 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/markdown/RawOutputFragment.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.markdown 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.text.method.ScrollingMovementMethod 6 | import android.view.View 7 | import com.exozet.android.core.R 8 | import com.exozet.android.core.base.BaseFragment 9 | import com.exozet.android.core.extensions.onClick 10 | import com.exozet.android.core.storage.bundle 11 | import kotlinx.android.synthetic.main.fragment_raw_output.* 12 | import net.kibotu.logger.Logger.logv 13 | import net.kibotu.resourceextension.resString 14 | 15 | 16 | class RawOutputFragment : BaseFragment() { 17 | 18 | override val layout = R.layout.fragment_raw_output 19 | 20 | private var raw by bundle() 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | 25 | content.movementMethod = ScrollingMovementMethod() 26 | 27 | logv { "$raw" } 28 | content.text = "$raw" 29 | 30 | share.onClick { 31 | share() 32 | } 33 | } 34 | 35 | private fun share() = 36 | Intent().apply { 37 | action = Intent.ACTION_SEND 38 | putExtra(Intent.EXTRA_TEXT, raw) 39 | type = "text/plain" 40 | startActivity(Intent.createChooser(this, R.string.share_with.resString)) 41 | } 42 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/math/Line.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.math; 2 | 3 | /** 4 | * Created by jan.rabe on 27/07/16. 5 | */ 6 | 7 | public class Line { 8 | 9 | public Vector2 start; 10 | public Vector2 end; 11 | 12 | public Line(Vector2 start, Vector2 end) { 13 | this.start = start; 14 | this.end = end; 15 | } 16 | 17 | public Line setStart(Vector2 start) { 18 | this.start = start; 19 | return this; 20 | } 21 | 22 | public Line setEnd(Vector2 end) { 23 | this.end = end; 24 | return this; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | 32 | Line line = (Line) o; 33 | 34 | if (start != null ? !start.equals(line.start) : line.start != null) return false; 35 | return end != null ? end.equals(line.end) : line.end == null; 36 | 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | int result = start != null ? start.hashCode() : 0; 42 | result = 31 * result + (end != null ? end.hashCode() : 0); 43 | return result; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return start + " -> " + end; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/misc/AutoClearedValue.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.misc 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.lifecycle.Lifecycle 5 | import androidx.lifecycle.LifecycleObserver 6 | import androidx.lifecycle.OnLifecycleEvent 7 | import kotlin.properties.ReadWriteProperty 8 | import kotlin.reflect.KProperty 9 | 10 | /** 11 | * A lazy property that gets cleaned up when the fragment is destroyed. 12 | * 13 | * Accessing this variable in a destroyed fragment will throw NPE. 14 | */ 15 | class AutoClearedValue(val fragment: Fragment) : ReadWriteProperty { 16 | 17 | private var _value: T? = null 18 | 19 | init { 20 | fragment.lifecycle.addObserver(object : LifecycleObserver { 21 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 22 | fun onDestroy() { 23 | _value = null 24 | } 25 | }) 26 | } 27 | 28 | override fun getValue(thisRef: Fragment, property: KProperty<*>): T { 29 | return _value ?: throw IllegalStateException( 30 | "should never call auto-cleared-value get when it might not be available" 31 | ) 32 | } 33 | 34 | override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { 35 | _value = value 36 | } 37 | } 38 | 39 | /** 40 | * Creates an [AutoClearedValue] associated with this fragment. 41 | */ 42 | fun Fragment.autoCleared() = AutoClearedValue(this) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/misc/FakeDataGenerator.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("FakeDataGenerator") 2 | 3 | package com.exozet.android.core.misc 4 | 5 | import android.graphics.Color 6 | import android.graphics.drawable.ColorDrawable 7 | import com.exozet.android.core.utils.MathExtensions.random 8 | import com.exozet.android.core.utils.MathExtensions.randomBoolean 9 | import java.text.MessageFormat.format 10 | import kotlin.math.roundToInt 11 | 12 | /** 13 | * Created by Nyaruhodo on 07.05.2016. 14 | */ 15 | 16 | fun createRandomImageUrl(): String { 17 | 18 | val landscape = randomBoolean() 19 | val endpoint = randomBoolean() 20 | 21 | val width = random(300, 400) 22 | val height = random(200, 300) 23 | 24 | return format( 25 | if (endpoint) 26 | "https://lorempixel.com/{0}/{1}/" 27 | else 28 | "https://picsum.photos/{0}/{1}/", 29 | if (landscape) width else height, if (landscape) height else width 30 | ) 31 | } 32 | 33 | @JvmOverloads 34 | fun generateRandomColorDrawable(alpha: Int = 255, red: Int = 255, green: Int = 255, blue: Int = 255): ColorDrawable = ColorDrawable(generateRandomColor(alpha, red, green, blue)) 35 | 36 | @JvmOverloads 37 | fun generateRandomColor(alpha: Int = 255, red: Int = 255, green: Int = 255, blue: Int = 255): Int { 38 | 39 | var r = random(256) 40 | var g = random(256) 41 | var b = random(256) 42 | 43 | // mix the color 44 | r = ((r + red) / 2f).roundToInt() 45 | g = ((g + green) / 2f).roundToInt() 46 | b = ((b + blue) / 2f).roundToInt() 47 | 48 | return Color.argb(alpha, r, g, b) 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/misc/SynchronizedValue.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.misc; 2 | 3 | public class SynchronizedValue { 4 | 5 | private T t = null; 6 | private final Object lock = new Object(); 7 | 8 | public void set(T t) { 9 | synchronized (lock) { 10 | this.t = t; 11 | } 12 | } 13 | 14 | public T get() { 15 | synchronized (lock) { 16 | return t; 17 | } 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "SynchronizedValue{" + 23 | "t=" + get() + 24 | '}'; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | 32 | SynchronizedValue that = (SynchronizedValue) o; 33 | 34 | if (t != null ? !t.equals(that.t) : that.t != null) return false; 35 | return lock != null ? lock.equals(that.lock) : that.lock == null; 36 | 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | int result = t != null ? t.hashCode() : 0; 42 | result = 31 * result + (lock != null ? lock.hashCode() : 0); 43 | return result; 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/misc/UIDGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.misc 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | /** 6 | * Created by [Jan Rabe](https://about.me/janrabe). 7 | */ 8 | object UIDGenerator { 9 | 10 | private const val START_UID = 0 11 | 12 | /** 13 | * https://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurrent-map-examples/ 14 | */ 15 | private val nextUID = AtomicInteger(START_UID) 16 | 17 | fun newUID(): Int { 18 | if (!isValid(nextUID.get())) { 19 | throw IllegalStateException("UID pool depleted") 20 | } 21 | return nextUID.incrementAndGet() 22 | } 23 | 24 | private fun isValid(uid: Int): Boolean = uid >= START_UID 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/misc/WeakReferenceDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.misc 2 | 3 | import java.lang.ref.WeakReference 4 | import kotlin.reflect.KProperty 5 | 6 | inline fun weak() = WeakReferenceDelegate() 7 | 8 | inline fun weak(value: T) = WeakReferenceDelegate(value) 9 | 10 | class WeakReferenceDelegate { 11 | 12 | private var weakReference: WeakReference? = null 13 | 14 | constructor() 15 | 16 | constructor(value: T) { 17 | weakReference = WeakReference(value) 18 | } 19 | 20 | operator fun getValue(thisRef: Any, property: KProperty<*>): T? = weakReference?.get() 21 | 22 | operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) { 23 | weakReference = WeakReference(value) 24 | } 25 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/models/LoadingInfo.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.models 2 | 3 | import org.parceler.Parcel 4 | 5 | @Parcel(Parcel.Serialization.BEAN) 6 | data class LoadingInfo( 7 | val name: String = "", 8 | val isLoading: Boolean = true, 9 | val time: Long = System.currentTimeMillis() 10 | ) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/realm/RealmDao.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.realm 2 | 3 | import io.realm.Realm 4 | import io.realm.RealmModel 5 | import io.realm.RealmResults 6 | import io.realm.kotlin.deleteFromRealm 7 | 8 | /** 9 | * {@inheritDoc} 10 | */ 11 | open class RealmDao(val realm: Realm) { 12 | 13 | /** 14 | * Gets all [T]. 15 | */ 16 | inline fun all(): RealmResults = realm.where(T::class.java).findAllAsync() 17 | 18 | /** 19 | * Updates and inserts [T]. 20 | */ 21 | open fun insertOrUpdate(item: T) = realm.executeTransactionAsync { it.insertOrUpdate(item) } 22 | 23 | /** 24 | * Updates and inserts list of [T]. 25 | */ 26 | open fun insertOrUpdate(items: List) = realm.executeTransactionAsync { it.insertOrUpdate(items) } 27 | 28 | /** 29 | * Runs realm transaction so we can update managed objects directly. 30 | */ 31 | open fun update(item: T, block: (item: T) -> Unit) { realm.executeTransaction { block(item) } } 32 | 33 | /** 34 | * Deletes [T]. 35 | */ 36 | open fun delete(item: T) = item.deleteFromRealm() 37 | 38 | /** 39 | * Deletes all [T]. 40 | */ 41 | inline fun deleteAll() = realm.executeTransactionAsync { realm -> 42 | realm.where(T::class.java) 43 | .findAllAsync() 44 | .deleteAllFromRealm() 45 | } 46 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/realm/RealmModelLiveData.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.realm 2 | 3 | import androidx.lifecycle.LiveData 4 | import io.realm.RealmChangeListener 5 | import io.realm.RealmModel 6 | import io.realm.RealmObject 7 | import io.realm.kotlin.addChangeListener 8 | import io.realm.kotlin.removeChangeListener 9 | 10 | open class RealmModelLiveData(val model: T, var valueOnActive: Boolean = false) : LiveData() { 11 | 12 | protected val listener = RealmChangeListener { postValue(it.freeze()) } 13 | 14 | override fun onActive() { 15 | model.addChangeListener(listener) 16 | if (valueOnActive) 17 | value = model 18 | } 19 | 20 | override fun onInactive() = model.removeChangeListener(listener) 21 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/realm/RealmRepository.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.realm 2 | 3 | import androidx.annotation.WorkerThread 4 | import io.realm.RealmModel 5 | import io.realm.RealmResults 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | 9 | abstract class RealmRepository, T : RealmModel> { 10 | 11 | abstract val db: Dao 12 | 13 | /** 14 | * Gets all [T]. 15 | */ 16 | inline fun all(): RealmResults = db.all() 17 | 18 | /** 19 | * Updates and inserts [T]. 20 | */ 21 | fun insertOrUpdate(item: T) = db.insertOrUpdate(item) 22 | 23 | /** 24 | * Updates and inserts list of [T]. 25 | */ 26 | fun insertOrUpdate(items: List) = db.insertOrUpdate(items) 27 | 28 | /** 29 | * Deletes [T]. 30 | */ 31 | fun delete(item: T) = db.delete(item) 32 | 33 | /** 34 | * Deletes all [T]. 35 | */ 36 | inline fun deleteAll() = db.deleteAll() 37 | 38 | /** 39 | * Loads [T] from network and inserts into [Dao]. 40 | */ 41 | suspend inline fun loadAndUpdate() { 42 | val result = withContext(Dispatchers.IO) { 43 | load() 44 | } ?: return 45 | 46 | db.insertOrUpdate(result) 47 | } 48 | 49 | /** 50 | * Loads [T] on [Dispatchers.IO] thread. 51 | */ 52 | @WorkerThread 53 | open suspend fun load(): T? = TODO() 54 | 55 | /** 56 | * Loads [T] from network and inserts into [Dao]. 57 | */ 58 | suspend inline fun > loadAndUpdateList() { 59 | val result = withContext(Dispatchers.IO) { 60 | loadList() 61 | } ?: return 62 | 63 | db.insertOrUpdate(result) 64 | } 65 | 66 | /** 67 | * Loads a list of [T] on [Dispatchers.IO] thread. 68 | */ 69 | @WorkerThread 70 | open suspend fun loadList(): List? = TODO() 71 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/realm/RealmResultsLiveData.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.realm 2 | 3 | import androidx.lifecycle.LiveData 4 | import io.realm.RealmChangeListener 5 | import io.realm.RealmModel 6 | import io.realm.RealmObject 7 | import io.realm.RealmResults 8 | 9 | 10 | open class RealmResultsLiveData(protected val results: RealmResults, var valueOnActive: Boolean = false) : LiveData>() { 11 | 12 | protected val listener = RealmChangeListener> { postValue(it.freeze()) } 13 | 14 | override fun onActive() { 15 | results.addChangeListener(listener) 16 | if (valueOnActive) 17 | value = results.freeze() 18 | } 19 | 20 | override fun onInactive() = results.removeChangeListener(listener) 21 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/connectivity/LiveNetworkMonitor.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.connectivity 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import net.kibotu.ContextHelper 6 | 7 | object LiveNetworkMonitor { 8 | 9 | val isConnected: Boolean 10 | get() { 11 | val cm = ContextHelper.getApplication()?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 12 | @Suppress("DEPRECATION") 13 | return cm.activeNetworkInfo?.isAvailable == true && cm.activeNetworkInfo?.isConnectedOrConnecting == true 14 | } 15 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/connectivity/NetworkChangeReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.connectivity 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.net.ConnectivityManager 8 | 9 | class NetworkChangeReceiver(var onConnectivityUpdate: ((Boolean) -> Unit)? = null) : BroadcastReceiver() { 10 | 11 | override fun onReceive(context: Context?, intent: Intent?) { 12 | onConnectivityUpdate?.invoke(LiveNetworkMonitor.isConnected) 13 | } 14 | 15 | fun unregister(context: Context) { 16 | context.unregisterReceiver(this) 17 | } 18 | 19 | companion object { 20 | 21 | fun register(context: Context, onConnectivityUpdate: ((Boolean) -> Unit)? = null): NetworkChangeReceiver = NetworkChangeReceiver(onConnectivityUpdate) 22 | .also { 23 | // https://developer.android.com/training/monitoring-device-state/connectivity-monitoring#MonitorChanges 24 | @Suppress("DEPRECATION") 25 | context.registerReceiver(it, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/connectivity/NoNetworkException.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.connectivity 2 | 3 | class NoNetworkException : Exception() -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/crypto/Blowfish.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.crypto 2 | 3 | import android.util.Base64 4 | import net.kibotu.logger.Logger 5 | import javax.crypto.Cipher 6 | import javax.crypto.spec.IvParameterSpec 7 | import javax.crypto.spec.SecretKeySpec 8 | 9 | object Blowfish { 10 | 11 | private const val ALGORITHM = "Blowfish" 12 | private const val MODE = "Blowfish/CBC/PKCS5Padding" 13 | private const val IV = "abcdefgh" 14 | 15 | /** 16 | * e.g. class App : MultiDexApplication() { init { Blowfish.KEY = "mysecurepassword" } } 17 | */ 18 | var KEY = "changemeplease" 19 | 20 | fun encrypt(value: String) = try { 21 | val secretKeySpec = SecretKeySpec(KEY.toByteArray(), ALGORITHM) 22 | val cipher = Cipher.getInstance(MODE) 23 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(IV.toByteArray())) 24 | val values = cipher.doFinal(value.trimMargin().toByteArray()) 25 | Base64.encodeToString(values, Base64.DEFAULT).trimMargin() 26 | } catch (e: Exception) { 27 | Logger.e(e) 28 | "" 29 | } 30 | 31 | fun decrypt(value: String?) = try { 32 | val values = Base64.decode(value, Base64.DEFAULT) 33 | val secretKeySpec = SecretKeySpec(KEY.toByteArray(), ALGORITHM) 34 | val cipher = Cipher.getInstance(MODE) 35 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IvParameterSpec(IV.toByteArray())) 36 | String(cipher.doFinal(values)).trimMargin() 37 | } catch (e: Exception) { 38 | Logger.e(e) 39 | "" 40 | } 41 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/GoogleApi.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network 2 | 3 | import com.exozet.android.core.services.network.fcm.FcmMessage 4 | import io.reactivex.Observable 5 | import okhttp3.ResponseBody 6 | import retrofit2.http.Body 7 | import retrofit2.http.Header 8 | import retrofit2.http.Headers 9 | import retrofit2.http.POST 10 | 11 | interface GoogleApi { 12 | 13 | /** 14 | * https://developers.google.com/cloud-messaging/http 15 | */ 16 | @Headers("Content-Type: application/json") 17 | @POST("/gcm/send") 18 | fun sendPushNotification(@Body message: FcmMessage, @Header("Authorization") apiKey: String): Observable 19 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/NullableConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | 6 | import io.reactivex.annotations.Nullable; 7 | import okhttp3.ResponseBody; 8 | import retrofit2.Converter; 9 | import retrofit2.Retrofit; 10 | 11 | public class NullableConverterFactory extends Converter.Factory { 12 | @Nullable 13 | @Override 14 | public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 15 | final Converter delegate = retrofit.nextResponseBodyConverter(this, type, annotations); 16 | return (Converter) body -> body.contentLength() != 0 ? delegate.convert(body) : null; 17 | } 18 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/fcm/FcmMessage.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.fcm 2 | 3 | import org.parceler.Parcel 4 | 5 | /** 6 | * FCM message 7 | * 8 | * { 9 | * "to": "/topics/global", 10 | * "notification": { 11 | * "title": "hello", 12 | * "text": "world" 13 | * }, 14 | * "data": { 15 | * "key1": "value1", 16 | * "key2": "value2" 17 | * } 18 | * } 19 | * 20 | */ 21 | @Parcel(Parcel.Serialization.BEAN) 22 | data class FcmMessage( 23 | val to: String? = null, 24 | val notification: NotificationContent? = null, 25 | val data: Map? = null 26 | ) { 27 | constructor(recipient: String, title: String, text: String, data: Map? = null) 28 | : this(recipient, NotificationContent(title, text), data) 29 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/fcm/NotificationContent.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.fcm 2 | 3 | import org.parceler.Parcel 4 | 5 | @Parcel(Parcel.Serialization.BEAN) 6 | data class NotificationContent( 7 | val title: String? = null, 8 | val text: String? = null 9 | ) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/interceptors/AuthorizationInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.interceptors 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | 6 | class AuthorizationInterceptor(private val tokenProvider: () -> String?) : Interceptor { 7 | 8 | override fun intercept(chain: Interceptor.Chain): Response { 9 | var request = chain.request() 10 | 11 | // add jwt token 12 | if (request.header("Authorization") == "Bearer") { 13 | request = request.newBuilder().removeHeader("Authorization") 14 | .addHeader("Authorization", "Bearer ${tokenProvider()}") 15 | .build() 16 | } 17 | 18 | return chain.proceed(request) 19 | } 20 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/interceptors/ContentTypeInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.interceptors 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | 6 | class ContentTypeInterceptor(private val contentType: String = "application/json") : Interceptor { 7 | 8 | override fun intercept(chain: Interceptor.Chain): Response = chain 9 | .proceed(with( 10 | chain.request() 11 | .newBuilder() 12 | ) { 13 | header("Content-Type", contentType) 14 | .build() 15 | }) 16 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/interceptors/EmptyInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.interceptors 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | 6 | class EmptyInterceptor : Interceptor { 7 | override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request()) 8 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/interceptors/ErrorInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.interceptors 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | 6 | class ErrorInterceptor(val onError: (Response) -> Unit) : Interceptor { 7 | 8 | override fun intercept(chain: Interceptor.Chain): Response { 9 | 10 | val response = chain.proceed(chain.request()) 11 | 12 | return when { 13 | !response.isSuccessful -> { 14 | onError(response) 15 | Response.Builder().build() 16 | } 17 | else -> response 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/interceptors/InterceptorFactory.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("InterceptorFactory") 2 | 3 | package com.exozet.android.core.services.network.interceptors 4 | 5 | import com.github.simonpercic.oklog3.OkLogInterceptor 6 | import net.kibotu.logger.Logger 7 | import okhttp3.OkHttpClient 8 | import okhttp3.logging.HttpLoggingInterceptor 9 | 10 | fun createHttpLoggingInterceptor(enableLogging: () -> Boolean): HttpLoggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { 11 | override fun log(message: String) { 12 | Logger.v(message) 13 | } 14 | }).apply { 15 | level = if (enableLogging()) 16 | HttpLoggingInterceptor.Level.BODY 17 | else 18 | HttpLoggingInterceptor.Level.NONE 19 | } 20 | 21 | @Deprecated("removed, use app/res/xml/network-config.xml for certificates", replaceWith = ReplaceWith("")) 22 | fun OkHttpClient.Builder.addCertificates(certificate: String? = null, dangerouslyTrustingAllHosts: Boolean = false): OkHttpClient.Builder = this 23 | 24 | fun createOKLogInterceptor() = OkLogInterceptor.builder() 25 | .withRequestHeaders(true) 26 | .withResponseHeaders(true) 27 | .build() -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/interceptors/LoadingInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.interceptors 2 | 3 | import com.exozet.android.core.models.LoadingInfo 4 | import okhttp3.Interceptor 5 | import okhttp3.Response 6 | 7 | class LoadingInterceptor(private val onLoading: (LoadingInfo) -> Unit) : Interceptor { 8 | 9 | override fun intercept(chain: Interceptor.Chain): Response { 10 | 11 | val request = chain.request() 12 | 13 | val loadingInfo = LoadingInfo(name = request.url.encodedPath) 14 | onLoading(loadingInfo) 15 | 16 | try { 17 | return chain.proceed(request) 18 | } catch (e: Exception) { 19 | throw e 20 | } finally { 21 | onLoading(LoadingInfo(loadingInfo.name, false)) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/network/interceptors/UrlDecodedInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.network.interceptors 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.RequestBody 5 | import okhttp3.Response 6 | import okio.Buffer 7 | import java.io.IOException 8 | 9 | class UrlDecodedInterceptor : Interceptor { 10 | 11 | @Throws(IOException::class) 12 | override fun intercept(chain: Interceptor.Chain): Response { 13 | val original = chain.request() 14 | 15 | val postBody = bodyToString(original.body) 16 | val body = original.body 17 | var requestBody: RequestBody? = null 18 | 19 | if (body != null) { 20 | requestBody = RequestBody.create(original.body!!.contentType(), postBody) 21 | } 22 | 23 | val request = when { 24 | original.method == "post" -> original.newBuilder() 25 | .method(original.method, original.body) 26 | .post(requestBody!!) 27 | original.method == "put" -> original.newBuilder() 28 | .method(original.method, original.body) 29 | .put(requestBody!!) 30 | else -> original.newBuilder() 31 | .method(original.method, original.body) 32 | } 33 | 34 | return chain.proceed(request.build()) 35 | } 36 | 37 | fun bodyToString(request: RequestBody?): String { 38 | return try { 39 | val buffer = Buffer() 40 | if (request != null) 41 | request.writeTo(buffer) 42 | else 43 | return "" 44 | buffer.readUtf8() 45 | } catch (e: IOException) { 46 | "did not work" 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/notifications/FBNotificationJobService.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.notifications 2 | 3 | import com.firebase.jobdispatcher.JobParameters 4 | import com.firebase.jobdispatcher.JobService 5 | import net.kibotu.logger.Logger 6 | 7 | 8 | /** 9 | * Created by [Jan Rabe](https://about.me/janrabe). 10 | */ 11 | class FBNotificationJobService : JobService() { 12 | 13 | override fun onStartJob(job: JobParameters?): Boolean { 14 | Logger.d(TAG, "[onStartJob] Performing long running task in scheduled job") 15 | // TODO(developer): add long running task here. 16 | return false 17 | } 18 | 19 | override fun onStopJob(job: JobParameters?): Boolean { 20 | Logger.d(TAG, "[onStopJob] Performing long running task in scheduled job") 21 | return false 22 | } 23 | 24 | companion object { 25 | val TAG: String = FBNotificationJobService::class.java.simpleName 26 | } 27 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/services/sms/SmsReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.services.sms 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Build 7 | import android.provider.Telephony 8 | import android.telephony.SmsMessage 9 | 10 | /** 11 | * Created by habib.birdal on 07.02.18. 12 | * 13 | * 16 | * 17 | * 18 | * 19 | * 20 | * 21 | */ 22 | 23 | abstract class SmsReceiver : BroadcastReceiver() { 24 | 25 | override fun onReceive(context: Context, intent: Intent) { 26 | 27 | if (intent.extras == null) 28 | return 29 | 30 | if (Build.VERSION.SDK_INT < 19) 31 | return 32 | 33 | // @see https://developer.android.com/reference/android/telephony/SmsMessage.html 34 | val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent) 35 | 36 | onSmsMessages(messages) 37 | } 38 | 39 | abstract fun onSmsMessages(messages: Array) 40 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/storage/FirebaseDatabaseHelper.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.storage 2 | 3 | import com.google.firebase.database.FirebaseDatabase 4 | 5 | /** 6 | * Created by Jan Rabe. 7 | */ 8 | 9 | object FirebaseDatabaseHelper { 10 | 11 | val database by lazy { FirebaseDatabase.getInstance() } 12 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/storage/SecurePreferencesLongString.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.storage 2 | 3 | import android.content.Context 4 | import de.adorsys.android.securestoragelibrary.SecurePreferences 5 | 6 | 7 | private const val chunkSize = 240 8 | 9 | private fun getNumberOfChunksKey(key: String) = "${key}_numberOfChunks" 10 | 11 | internal fun Context.setLongStringValue(key: String, value: String) { 12 | val chunks = value.chunked(chunkSize) 13 | 14 | SecurePreferences.setValue(this, getNumberOfChunksKey(key), chunks.size) 15 | 16 | chunks.forEachIndexed { index, chunk -> 17 | SecurePreferences.setValue(this, "$key$index", chunk) 18 | } 19 | } 20 | 21 | internal fun Context.getLongStringValue(key: String): String? { 22 | val numberOfChunks = SecurePreferences.getIntValue(this, getNumberOfChunksKey(key), 0) 23 | 24 | if (numberOfChunks == 0) { 25 | return null 26 | } 27 | 28 | return (0 until numberOfChunks) 29 | .map { index -> 30 | val string = SecurePreferences.getStringValue(this, "$key$index", null) ?: run { 31 | return null 32 | } 33 | 34 | string 35 | }.reduce { accumulator, chunk -> accumulator + chunk } 36 | } 37 | 38 | internal fun Context.removeLongStringValue(key: String) { 39 | val numberOfChunks = SecurePreferences.getIntValue(this, getNumberOfChunksKey(key), 0) 40 | 41 | (0 until numberOfChunks).map { SecurePreferences.removeValue(this, "$key$it") } 42 | SecurePreferences.removeValue(this, getNumberOfChunksKey(key)) 43 | } 44 | 45 | internal fun Context.containsLongStringValue(key: String): Boolean = SecurePreferences.contains(this, getNumberOfChunksKey(key)) 46 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/custom/BottomNavigationBehavior.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.custom 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.Gravity 6 | import android.view.View 7 | import androidx.coordinatorlayout.widget.CoordinatorLayout 8 | import androidx.core.view.ViewCompat 9 | import com.google.android.material.snackbar.Snackbar 10 | import kotlin.math.max 11 | import kotlin.math.min 12 | 13 | open class BottomNavigationBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior(context, attrs) { 14 | 15 | override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int): Boolean { 16 | return axes == ViewCompat.SCROLL_AXIS_VERTICAL 17 | } 18 | 19 | override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { 20 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) 21 | child.translationY = max(0f, min(child.height.toFloat(), child.translationY + dy)) 22 | } 23 | 24 | override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean { 25 | if (dependency is Snackbar.SnackbarLayout) { 26 | updateSnackbar(child, dependency) 27 | } 28 | return super.layoutDependsOn(parent, child, dependency) 29 | } 30 | 31 | private fun updateSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) { 32 | if (snackbarLayout.layoutParams is CoordinatorLayout.LayoutParams) { 33 | val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams 34 | 35 | params.anchorId = child.id 36 | params.anchorGravity = Gravity.TOP 37 | params.gravity = Gravity.TOP 38 | snackbarLayout.layoutParams = params 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/custom/CustomBottomSheetBehavior.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.custom 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import androidx.coordinatorlayout.widget.CoordinatorLayout 7 | import com.google.android.material.bottomsheet.BottomSheetBehavior 8 | import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED 9 | import net.kibotu.logger.Logger.i 10 | 11 | /** 12 | * Custom behavior to inflate layout with [STATE_HALF_EXPANDED] state. 13 | */ 14 | open class CustomBottomSheetBehavior : BottomSheetBehavior { 15 | 16 | constructor() : super() 17 | 18 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) 19 | 20 | init { 21 | isHideable = false 22 | // we force initialization to be half expanded, default seems to be expanded 23 | state = STATE_HALF_EXPANDED 24 | } 25 | 26 | override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean { 27 | i("layoutDependsOn parent=$parent child=$child dependency=$dependency") 28 | return super.layoutDependsOn(parent, child, dependency) 29 | } 30 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/custom/CustomCardView.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.custom 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Path 6 | import android.graphics.RectF 7 | import android.util.AttributeSet 8 | import com.exozet.android.core.R 9 | import com.google.android.material.card.MaterialCardView 10 | import com.google.android.material.shape.CornerFamily 11 | import com.google.android.material.shape.ShapeAppearanceModel 12 | import com.google.android.material.shape.ShapeAppearancePathProvider 13 | import net.kibotu.resourceextension.dp 14 | 15 | 16 | /** A Card view that clips the content of any shape, this should be done upstream in card, 17 | * working around it for now. 18 | */ 19 | open class CustomCardView @JvmOverloads constructor( 20 | context: Context, 21 | attrs: AttributeSet? = null, 22 | defStyle: Int = R.attr.materialCardViewStyle 23 | ) : MaterialCardView(context, attrs, defStyle) { 24 | 25 | private val pathProvider = ShapeAppearancePathProvider() 26 | 27 | private val path: Path = Path() 28 | 29 | // context, attrs, defStyle, R.style.Widget_MaterialComponents_CardView 30 | private val shapeAppearance: ShapeAppearanceModel = ShapeAppearanceModel 31 | .Builder() 32 | .setTopLeftCorner(CornerFamily.ROUNDED, 6f.dp) 33 | .setTopRightCorner(CornerFamily.ROUNDED, 6f.dp) 34 | .build() 35 | 36 | private val rectF = RectF(0f, 0f, 0f, 0f) 37 | 38 | override fun onDraw(canvas: Canvas?) { 39 | rectF.right = width.toFloat() 40 | rectF.bottom = height.toFloat() 41 | pathProvider.calculatePath(shapeAppearance, 1f, rectF, path) 42 | canvas!!.clipPath(path) 43 | super.onDraw(canvas) 44 | } 45 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/custom/DisableableAppBarLayoutBehavior.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.custom 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import androidx.coordinatorlayout.widget.CoordinatorLayout 7 | import com.google.android.material.appbar.AppBarLayout 8 | 9 | open class DisableableAppBarLayoutBehavior(context: Context? = null, attrs: AttributeSet? = null) : AppBarLayout.Behavior(context, attrs) { 10 | 11 | var isEnabled = true 12 | 13 | override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean { 14 | return isEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type) 15 | } 16 | 17 | override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int, consumed: IntArray) { 18 | if (!isEnabled) return 19 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed) 20 | } 21 | 22 | override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { 23 | if (!isEnabled) return 24 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) 25 | } 26 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/custom/NonSwipeableViewPager.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.custom 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import androidx.viewpager.widget.ViewPager 7 | 8 | class NonSwipeableViewPager constructor(context: Context, attrs: AttributeSet?) : ViewPager(context, attrs) { 9 | 10 | override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean = false 11 | 12 | override fun onTouchEvent(ev: MotionEvent?): Boolean = false 13 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/custom/ScrollListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.custom 2 | 3 | import android.view.GestureDetector 4 | import android.view.MotionEvent 5 | import com.exozet.android.core.extensions.px 6 | import net.kibotu.resourceextension.dp 7 | 8 | open class ScrollListener(val width: () -> Int, val height: () -> Int) : GestureDetector.SimpleOnGestureListener() { 9 | 10 | var onScroll: ((percentX: Float, percentY: Float) -> Unit)? = null 11 | /** 12 | * Min Swipe X-Distance 13 | */ 14 | var thresholdX = 3.dp 15 | 16 | /** 17 | * Min Swipe Y-Distance 18 | */ 19 | var thresholdY = 3.dp 20 | 21 | /** 22 | * Starting X-Position 23 | */ 24 | protected var startX = 0f 25 | 26 | /** 27 | * Starting Y-Position 28 | */ 29 | protected var startY = 0f 30 | 31 | override fun onDown(e: MotionEvent?): Boolean { 32 | startX = e?.x ?: 0f 33 | startY = e?.y ?: 0f 34 | return true 35 | } 36 | 37 | override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean { 38 | val dX = startX - (e2?.x ?: 0f) 39 | val dY = startY - (e2?.y ?: 0f) 40 | 41 | return when { 42 | Math.abs(dX) <= thresholdX && Math.abs(dY) <= thresholdY -> false 43 | else -> { 44 | 45 | val percentX = (dX) / width() 46 | val percentY = (dY) / height() 47 | 48 | onScroll?.invoke(percentX, percentY) 49 | 50 | true 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/custom/ZoomageView.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.custom 2 | 3 | import android.content.Context 4 | import android.content.res.Configuration 5 | import android.util.AttributeSet 6 | import android.view.MotionEvent 7 | import android.view.View 8 | 9 | open class ZoomageView @JvmOverloads constructor( 10 | context: Context, 11 | attrs: AttributeSet? = null, 12 | defStyleAttr: Int = 0, 13 | defStyleRes: Int = 0 14 | ) : com.jsibbold.zoomage.ZoomageView(context, attrs, defStyleAttr) { 15 | 16 | internal var view: View? = null 17 | 18 | override fun onTouchEvent(event: MotionEvent?): Boolean = when { 19 | event?.pointerCount ?: 0 <= 1 -> { 20 | view?.onTouchEvent(event) 21 | isTranslatable = false // disable translation while we have only one finger on screen 22 | super.onTouchEvent(event) 23 | } 24 | else -> { 25 | 26 | // consume touch event inside scroll container like view pagers 27 | parent.requestDisallowInterceptTouchEvent(true) 28 | 29 | isTranslatable = true 30 | super.onTouchEvent(event) 31 | } 32 | } 33 | 34 | override fun onConfigurationChanged(newConfig: Configuration) { 35 | super.onConfigurationChanged(newConfig) 36 | scaleType = ScaleType.FIT_CENTER 37 | } 38 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/itemDecorator/GridItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.itemDecorator 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | /*** 8 | * Made by Lokesh Desai (Android4Dev) 9 | * https://www.android4dev.com/how-to-use-recyclerview-with-gridlayoutmanager-android/ 10 | */ 11 | class GridItemDecoration(private val gridSpacing: Int, private val gridSize: Int) : RecyclerView.ItemDecoration() { 12 | 13 | private var needLeftSpacing = false 14 | 15 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { 16 | val frameWidth = ((parent.width - gridSpacing.toFloat() * (gridSize - 1)) / gridSize).toInt() 17 | val padding = parent.width / gridSize - frameWidth 18 | val itemPosition = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition 19 | if (itemPosition < gridSize) { 20 | outRect.top = 0 21 | } else { 22 | outRect.top = gridSpacing 23 | } 24 | if (itemPosition % gridSize == 0) { 25 | outRect.left = 0 26 | outRect.right = padding 27 | needLeftSpacing = true 28 | } else if ((itemPosition + 1) % gridSize == 0) { 29 | needLeftSpacing = false 30 | outRect.right = 0 31 | outRect.left = padding 32 | } else if (needLeftSpacing) { 33 | needLeftSpacing = false 34 | outRect.left = gridSpacing - padding 35 | if ((itemPosition + 2) % gridSize == 0) { 36 | outRect.right = gridSpacing - padding 37 | } else { 38 | outRect.right = gridSpacing / 2 39 | } 40 | } else if ((itemPosition + 2) % gridSize == 0) { 41 | needLeftSpacing = false 42 | outRect.left = gridSpacing / 2 43 | outRect.right = gridSpacing - padding 44 | } else { 45 | needLeftSpacing = false 46 | outRect.left = gridSpacing / 2 47 | outRect.right = gridSpacing / 2 48 | } 49 | outRect.bottom = 0 50 | } 51 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/itemDecorator/GridSpacingItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.itemDecorator; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { 10 | 11 | private final int spanCount; 12 | private final int spacing; 13 | private final boolean includeEdge; 14 | 15 | public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { 16 | this.spanCount = spanCount; 17 | this.spacing = spacing; 18 | this.includeEdge = includeEdge; 19 | } 20 | 21 | @Override 22 | public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 23 | int position = parent.getChildAdapterPosition(view); // item position 24 | int column = position % spanCount; // item column 25 | 26 | if (includeEdge) { 27 | outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) 28 | outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) 29 | 30 | if (position < spanCount) { // top edge 31 | outRect.top = spacing; 32 | } 33 | outRect.bottom = spacing; // item bottom 34 | } else { 35 | outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) 36 | outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) 37 | if (position >= spanCount) { 38 | outRect.top = spacing; // item top 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/itemDecorator/SpacesItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.itemDecorator; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import android.view.View; 6 | 7 | import androidx.recyclerview.widget.GridLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | /** 11 | * Created by Au61 on 2016/1/15. 12 | */ 13 | public class SpacesItemDecoration extends RecyclerView.ItemDecoration { 14 | 15 | private SpacesItemDecorationEntrust mEntrust; 16 | private int mColor; 17 | private int leftRight; 18 | private int topBottom; 19 | 20 | 21 | public SpacesItemDecoration(int leftRight, int topBottom) { 22 | this.leftRight = leftRight; 23 | this.topBottom = topBottom; 24 | } 25 | 26 | public SpacesItemDecoration(int leftRight, int topBottom, int mColor) { 27 | this(leftRight, topBottom); 28 | this.mColor = mColor; 29 | } 30 | 31 | @Override 32 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 33 | if (mEntrust == null) { 34 | mEntrust = getEntrust(parent.getLayoutManager()); 35 | } 36 | mEntrust.onDraw(c, parent, state); 37 | super.onDraw(c, parent, state); 38 | } 39 | 40 | @Override 41 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 42 | if (mEntrust == null) { 43 | mEntrust = getEntrust(parent.getLayoutManager()); 44 | } 45 | mEntrust.getItemOffsets(outRect, view, parent, state); 46 | } 47 | 48 | private SpacesItemDecorationEntrust getEntrust(RecyclerView.LayoutManager manager) { 49 | SpacesItemDecorationEntrust entrust = null; 50 | //要注意这边的GridLayoutManager是继承LinearLayoutManager,所以要先判断GridLayoutManager 51 | if (manager instanceof GridLayoutManager) { 52 | entrust = new GridEntrust(leftRight, topBottom, mColor); 53 | } else {//其他的都当做Linear来进行计算 54 | entrust = new LinearEntrust(leftRight, topBottom, mColor); 55 | } 56 | return entrust; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/itemDecorator/SpacesItemDecorationEntrust.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.itemDecorator; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.graphics.drawable.Drawable; 7 | import android.view.View; 8 | 9 | import androidx.recyclerview.widget.RecyclerView; 10 | 11 | /** 12 | * 作者:请叫我百米冲刺 on 2016/12/6 上午11:37 13 | * 邮箱:mail@hezhilin.cc 14 | */ 15 | 16 | public abstract class SpacesItemDecorationEntrust { 17 | 18 | //color的传入方式是resouce.getcolor 19 | protected Drawable mDivider; 20 | 21 | protected int leftRight; 22 | 23 | protected int topBottom; 24 | 25 | public SpacesItemDecorationEntrust(int leftRight, int topBottom, int mColor) { 26 | this.leftRight = leftRight; 27 | this.topBottom = topBottom; 28 | if (mColor != 0) { 29 | mDivider = new ColorDrawable(mColor); 30 | } 31 | } 32 | 33 | 34 | abstract void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state); 35 | 36 | abstract void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state); 37 | 38 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleAnimationListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import android.view.animation.Animation 4 | 5 | open class SimpleAnimationListener : Animation.AnimationListener { 6 | 7 | override fun onAnimationStart(animation: Animation?) { 8 | } 9 | 10 | override fun onAnimationEnd(animation: Animation?) { 11 | } 12 | 13 | override fun onAnimationRepeat(animation: Animation?) { 14 | } 15 | } 16 | 17 | fun Animation.onAnimationStart(block: (Animation?) -> Unit) = setAnimationListener(object : SimpleAnimationListener() { 18 | override fun onAnimationStart(animation: Animation?) = block(animation) 19 | }) 20 | 21 | fun Animation.onAnimationEnd(block: (Animation?) -> Unit) = setAnimationListener(object : SimpleAnimationListener() { 22 | override fun onAnimationEnd(animation: Animation?) = block(animation) 23 | }) 24 | 25 | fun Animation.onAnimationRepeat(block: (Animation?) -> Unit) = setAnimationListener(object : SimpleAnimationListener() { 26 | override fun onAnimationRepeat(animation: Animation?) = block(animation) 27 | }) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleAnimatorListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import android.animation.Animator 4 | 5 | open class SimpleAnimatorListener : Animator.AnimatorListener { 6 | 7 | override fun onAnimationStart(animation: Animator?) { 8 | } 9 | 10 | override fun onAnimationEnd(animation: Animator?) { 11 | } 12 | 13 | override fun onAnimationCancel(animation: Animator?) { 14 | } 15 | 16 | override fun onAnimationRepeat(animation: Animator?) { 17 | } 18 | } 19 | 20 | @Deprecated("use androidktx", ReplaceWith("doOnStart")) 21 | fun Animator.onAnimationStart(block: (Animator?) -> Unit) = addListener(object : SimpleAnimatorListener() { 22 | override fun onAnimationStart(animation: Animator?) = block(animation) 23 | }) 24 | 25 | @Deprecated("use androidktx", ReplaceWith("doOnEnd")) 26 | fun Animator.onAnimationEnd(block: (Animator?) -> Unit) = addListener(object : SimpleAnimatorListener() { 27 | override fun onAnimationEnd(animation: Animator?) = block(animation) 28 | }) 29 | 30 | @Deprecated("use androidktx", ReplaceWith("doOnCancel")) 31 | fun Animator.onAnimationCancel(block: (Animator?) -> Unit) = addListener(object : SimpleAnimatorListener() { 32 | override fun onAnimationCancel(animation: Animator?) = block(animation) 33 | }) 34 | 35 | @Deprecated("use androidktx", ReplaceWith("doOnRepeat")) 36 | fun Animator.onAnimationRepeat(block: (Animator?) -> Unit) = addListener(object : SimpleAnimatorListener() { 37 | override fun onAnimationRepeat(animation: Animator?) = block(animation) 38 | }) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleBaseFragmentAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentStatePagerAdapter 6 | 7 | class SimpleBaseFragmentAdapter(fm: FragmentManager, val size: Int, fragmentProvider: () -> Fragment) : FragmentStatePagerAdapter(fm) { 8 | 9 | val fragments: List = (0 until size).map { fragmentProvider() } 10 | 11 | override fun getItem(position: Int): Fragment = fragments[position] 12 | 13 | override fun getCount(): Int = size 14 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleMotionListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import androidx.constraintlayout.motion.widget.MotionLayout 4 | import androidx.constraintlayout.motion.widget.MotionScene 5 | import io.reactivex.Observable 6 | 7 | /** 8 | * https://developer.android.com/reference/android/support/constraint/motion/MotionLayout.TransitionListener 9 | */ 10 | open class SimpleMotionListener : MotionLayout.TransitionListener { 11 | 12 | override fun onTransitionTrigger(motionLayout: MotionLayout, trigger: Int, positive: Boolean, progress: Float) { 13 | } 14 | 15 | override fun onTransitionStarted(motionLayout: MotionLayout, startId: Int, endId: Int) { 16 | } 17 | 18 | override fun onTransitionChange(motionLayout: MotionLayout, startId: Int, endId: Int, progress: Float) { 19 | } 20 | 21 | override fun onTransitionCompleted(motionLayout: MotionLayout, currentId: Int) { 22 | } 23 | } 24 | 25 | fun MotionLayout.onCollapseStateChanged(): Observable { 26 | 27 | var listener: MotionLayout.TransitionListener? 28 | 29 | return io.reactivex.subjects.BehaviorSubject.create { emitter -> 30 | 31 | if (emitter.isDisposed) 32 | return@create 33 | 34 | listener = object : SimpleMotionListener() { 35 | override fun onTransitionChange(motionLayout: MotionLayout, startId: Int, endId: Int, progress: Float) { 36 | emitter.onNext(progress) 37 | } 38 | } 39 | 40 | setTransitionListener(listener) 41 | 42 | }.doOnDispose { 43 | setTransitionListener(null) 44 | }.distinctUntilChanged() 45 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleOnCheckedChangeListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import android.widget.CompoundButton 4 | 5 | open class SimpleOnCheckedChangeListener : CompoundButton.OnCheckedChangeListener { 6 | override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { 7 | } 8 | } 9 | 10 | inline fun CompoundButton.onCheckedChanged(crossinline block: (Boolean) -> Unit) { 11 | setOnCheckedChangeListener(object : SimpleOnCheckedChangeListener() { 12 | override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) = block(isChecked) 13 | }) 14 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleOnItemSelectedListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import android.view.View 4 | import android.widget.Adapter 5 | import android.widget.AdapterView 6 | 7 | open class SimpleOnItemSelectedListener : AdapterView.OnItemSelectedListener { 8 | 9 | override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { 10 | } 11 | 12 | override fun onNothingSelected(parent: AdapterView<*>?) { 13 | } 14 | } 15 | 16 | inline fun AdapterView.onItemSelected(crossinline block: (parent: AdapterView<*>?, view: View?, position: Int, id: Long) -> Unit) where T : Adapter { 17 | onItemSelectedListener = object : SimpleOnItemSelectedListener() { 18 | override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = block(parent, view, position, id) 19 | } 20 | } 21 | 22 | inline fun AdapterView.onNothingSelected(crossinline block: (parent: AdapterView<*>?) -> Unit) where T : Adapter { 23 | 24 | onItemSelectedListener = object : SimpleOnItemSelectedListener() { 25 | override fun onNothingSelected(parent: AdapterView<*>?) = block(parent) 26 | } 27 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleOnPageChangeListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import androidx.viewpager.widget.ViewPager 4 | 5 | open class SimpleOnPageChangeListener : ViewPager.OnPageChangeListener { 6 | 7 | override fun onPageScrollStateChanged(state: Int) { 8 | } 9 | 10 | override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { 11 | } 12 | 13 | override fun onPageSelected(position: Int) { 14 | } 15 | } 16 | 17 | fun ViewPager.onPageScrollStateChanged(block: (Int) -> Unit) = addOnPageChangeListener(object : SimpleOnPageChangeListener() { 18 | override fun onPageScrollStateChanged(state: Int) = block(state) 19 | }) 20 | 21 | fun ViewPager.onPageScrolled(block: (Int, Float, Int) -> Unit) = addOnPageChangeListener(object : SimpleOnPageChangeListener() { 22 | override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = block(position, positionOffset, positionOffsetPixels) 23 | }) 24 | 25 | fun ViewPager.onPageSelected(block: (Int) -> Unit) = addOnPageChangeListener(object : SimpleOnPageChangeListener() { 26 | override fun onPageSelected(position: Int) = block(position) 27 | }) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleTabSelectionListener.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import com.google.android.material.tabs.TabLayout 4 | 5 | open class SimpleTabSelectionListener : TabLayout.OnTabSelectedListener { 6 | 7 | override fun onTabSelected(tab: TabLayout.Tab?) { 8 | } 9 | 10 | override fun onTabUnselected(tab: TabLayout.Tab?) { 11 | } 12 | 13 | override fun onTabReselected(tab: TabLayout.Tab?) { 14 | } 15 | } 16 | 17 | fun TabLayout.onTabSelected(block: (TabLayout.Tab?) -> Unit) = addOnTabSelectedListener(object : SimpleTabSelectionListener() { 18 | override fun onTabSelected(tab: TabLayout.Tab?) = block(tab) 19 | }) 20 | 21 | fun TabLayout.onTabUnselected(block: (TabLayout.Tab?) -> Unit) = addOnTabSelectedListener(object : SimpleTabSelectionListener() { 22 | override fun onTabUnselected(tab: TabLayout.Tab?) = block(tab) 23 | }) 24 | 25 | fun TabLayout.onTabReselected(block: (TabLayout.Tab?) -> Unit) = addOnTabSelectedListener(object : SimpleTabSelectionListener() { 26 | override fun onTabReselected(tab: TabLayout.Tab?) = block(tab) 27 | }) -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/ui/nullobjects/SimpleTextWatcher.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.ui.nullobjects 2 | 3 | import android.text.Editable 4 | import android.text.TextWatcher 5 | import android.widget.EditText 6 | 7 | open class SimpleTextWatcher : TextWatcher { 8 | 9 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { 10 | } 11 | 12 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { 13 | } 14 | 15 | override fun afterTextChanged(s: Editable?) { 16 | } 17 | } 18 | 19 | fun EditText.beforeTextChanged(block: (s: CharSequence?, start: Int, count: Int, after: Int) -> Unit): TextWatcher { 20 | val listener = object : SimpleTextWatcher() { 21 | 22 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { 23 | if (s?.isNotEmpty() == true) 24 | block(s, start, count, after) 25 | } 26 | } 27 | addTextChangedListener(listener) 28 | return listener 29 | } 30 | 31 | fun EditText.onTextChanged(block: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit): TextWatcher { 32 | 33 | val listener = object : SimpleTextWatcher() { 34 | 35 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { 36 | if (s?.isNotEmpty() == true) 37 | block(s, start, before, count) 38 | } 39 | } 40 | addTextChangedListener(listener) 41 | return listener 42 | } 43 | 44 | fun EditText.afterTextChanged(block: (s: CharSequence?) -> Unit): TextWatcher { 45 | val listener = object : SimpleTextWatcher() { 46 | 47 | override fun afterTextChanged(s: Editable?) { 48 | if (s?.isNotEmpty() == true) 49 | block(s) 50 | } 51 | } 52 | addTextChangedListener(listener) 53 | return listener 54 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/uid/PrimaryKeyGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.uid 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | /** 6 | * Created by [Jan Rabe](https://about.me/janrabe). 7 | */ 8 | class PrimaryKeyGenerator { 9 | 10 | private val START_UID = 0 11 | 12 | /** 13 | * https://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurrent-map-examples/ 14 | */ 15 | private val nextUID = AtomicInteger(START_UID) 16 | 17 | fun newUID(): Int { 18 | if (!isValid(nextUID.get())) { 19 | throw IllegalStateException("UID pool depleted") 20 | } 21 | return nextUID.incrementAndGet() 22 | } 23 | 24 | fun nextUID() = newUID().toString() 25 | 26 | private fun isValid(uid: Int): Boolean = uid >= START_UID 27 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/BitmapExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Matrix; 5 | import android.graphics.drawable.BitmapDrawable; 6 | import android.widget.ImageView; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | /** 11 | * Created by Jan Rabe on 24/09/15. 12 | */ 13 | final public class BitmapExtensions { 14 | 15 | private BitmapExtensions() throws IllegalAccessException { 16 | throw new IllegalAccessException(); 17 | } 18 | 19 | public static Bitmap flipHorizontally(@NonNull final Bitmap src) { 20 | final Matrix matrix = new Matrix(); 21 | matrix.preScale(1.0f, -1.0f); 22 | return createBitmapWithMatrix(src, matrix); 23 | } 24 | 25 | public static Bitmap flipVertically(@NonNull final Bitmap src) { 26 | final Matrix matrix = new Matrix(); 27 | matrix.preScale(-1.0f, 1.0f); 28 | return createBitmapWithMatrix(src, matrix); 29 | } 30 | 31 | public static Bitmap createBitmapWithMatrix(@NonNull final Bitmap src, @NonNull final Matrix matrix) { 32 | final Bitmap dst = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); 33 | if (src != dst) { 34 | // src.recycle(); 35 | } 36 | return dst; 37 | } 38 | 39 | public static Bitmap getBitmap(@NonNull final ImageView imageView) { 40 | return ((BitmapDrawable) imageView.getDrawable()).getBitmap(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/ByteExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | /** 4 | * Created by jan.rabe on 10/08/16. 5 | */ 6 | 7 | public class ByteExtensions { 8 | 9 | final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); 10 | 11 | public static String bytesToHex(byte[] bytes) { 12 | char[] hexChars = new char[bytes.length * 2]; 13 | for (int j = 0; j < bytes.length; j++) { 14 | int v = bytes[j] & 0xFF; 15 | hexChars[j * 2] = hexArray[v >>> 4]; 16 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 17 | } 18 | return new String(hexChars); 19 | } 20 | 21 | public static boolean validUTF8(byte[] input) { 22 | int i = 0; 23 | // Check for BOM 24 | if (input.length >= 3 && (input[0] & 0xFF) == 0xEF 25 | && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) { 26 | i = 3; 27 | } 28 | 29 | int end; 30 | for (int j = input.length; i < j; ++i) { 31 | int octet = input[i]; 32 | if ((octet & 0x80) == 0) { 33 | continue; // ASCII 34 | } 35 | 36 | // Check for UTF-8 leading byte 37 | if ((octet & 0xE0) == 0xC0) { 38 | end = i + 1; 39 | } else if ((octet & 0xF0) == 0xE0) { 40 | end = i + 2; 41 | } else if ((octet & 0xF8) == 0xF0) { 42 | end = i + 3; 43 | } else { 44 | // Java only supports BMP so 3 is max 45 | return false; 46 | } 47 | 48 | while (i < end) { 49 | i++; 50 | octet = input[i]; 51 | if ((octet & 0xC0) != 0x80) { 52 | // Not a valid trailing byte 53 | return false; 54 | } 55 | } 56 | } 57 | return true; 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/CalendarExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | import androidx.annotation.Nullable; 6 | 7 | import java.util.Calendar; 8 | import java.util.Date; 9 | import java.util.Locale; 10 | 11 | /** 12 | * Created by Jan Rabe on 01/10/15. 13 | */ 14 | final public class CalendarExtensions { 15 | 16 | public CalendarExtensions() throws IllegalAccessException { 17 | throw new IllegalAccessException(); 18 | } 19 | 20 | public static int getDayFromTimestamp(final long timestamp) { 21 | final Calendar cal = Calendar.getInstance(); 22 | cal.setTimeInMillis(timestamp); 23 | return cal.get(Calendar.DAY_OF_MONTH); 24 | } 25 | 26 | @NonNull 27 | public static Date[] getPastMonths(final int amount) { 28 | final Calendar calendar = Calendar.getInstance(); 29 | final Date[] result = new Date[amount]; 30 | for (int i = 0; i < amount; ++i) { 31 | result[i] = calendar.getTime(); 32 | calendar.add(Calendar.MONTH, -1); 33 | } 34 | return result; 35 | } 36 | 37 | public static int getYear(@Nullable final Date date, @NonNull final Locale locale) { 38 | if (date == null) 39 | return 0; 40 | 41 | final Calendar cal = Calendar.getInstance(locale); 42 | cal.setTime(date); 43 | return cal.get(Calendar.YEAR); 44 | } 45 | 46 | public static int getDay(@Nullable final Date date, @NonNull final Locale locale) { 47 | if (date == null) 48 | return 0; 49 | 50 | final Calendar cal = Calendar.getInstance(locale); 51 | cal.setTime(date); 52 | return cal.get(Calendar.DAY_OF_MONTH); 53 | } 54 | 55 | public static int getMonth(@Nullable final Date date, @NonNull final Locale locale) { 56 | if (date == null) 57 | return 0; 58 | 59 | final Calendar cal = Calendar.getInstance(locale); 60 | cal.setTime(date); 61 | return cal.get(Calendar.MONTH); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/DateExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | import androidx.annotation.Nullable; 6 | 7 | import java.text.DateFormat; 8 | import java.text.ParseException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Calendar; 11 | import java.util.Date; 12 | import java.util.GregorianCalendar; 13 | import java.util.Locale; 14 | import java.util.TimeZone; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * Created by Jan Rabe on 24/09/15. 19 | */ 20 | final public class DateExtensions { 21 | 22 | private DateExtensions() throws IllegalAccessException { 23 | throw new IllegalAccessException(); 24 | } 25 | 26 | /** 27 | * Format dd.MM.yy hh:mm e.g.: 24.09.15 12:26 28 | */ 29 | @Nullable 30 | public static Date parseDate(final String date) { 31 | final DateFormat df = new SimpleDateFormat("dd.MM.yy hh:mm"); 32 | Date result = null; 33 | try { 34 | result = df.parse(date); 35 | } catch (final ParseException e) { 36 | e.printStackTrace(); 37 | } 38 | return result; 39 | } 40 | 41 | @NonNull 42 | public static String parseDateInHoursMinutesDay(final Date date) { 43 | final DateFormat hours = new SimpleDateFormat("hh:mm"); 44 | final DateFormat day = new SimpleDateFormat("dd.MM.yy"); 45 | return hours.format(date) + " am " + day.format(date); 46 | } 47 | 48 | public static long getTimeZoneOffset() { 49 | return getTimeZoneOffset(Locale.getDefault()); 50 | } 51 | 52 | public static long getTimeZoneOffset(Locale locale) { 53 | Calendar calendar = new GregorianCalendar(locale); 54 | TimeZone timeZone = calendar.getTimeZone(); 55 | int GMTOffset = timeZone.getOffset(new Date().getTime()); 56 | return TimeUnit.MINUTES.convert(GMTOffset, TimeUnit.MILLISECONDS); 57 | } 58 | 59 | public static boolean usesDayLightSaving() { 60 | return TimeZone.getDefault().inDaylightTime(new Date()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/DrawableExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.drawable.BitmapDrawable; 5 | import android.graphics.drawable.Drawable; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import static net.kibotu.ContextHelper.getApplication; 10 | 11 | /** 12 | * Created by Nyaruhodo on 21.05.2016. 13 | */ 14 | public class DrawableExtensions { 15 | 16 | @Nullable 17 | public static Drawable scaleImage(@Nullable Drawable image, float scaleFactor) { 18 | 19 | if (!(image instanceof BitmapDrawable)) { 20 | return image; 21 | } 22 | 23 | Bitmap b = ((BitmapDrawable) image).getBitmap(); 24 | 25 | int sizeX = Math.round(image.getIntrinsicWidth() * scaleFactor); 26 | int sizeY = Math.round(image.getIntrinsicHeight() * scaleFactor); 27 | 28 | Bitmap bitmapResized = Bitmap.createScaledBitmap(b, sizeX, sizeY, false); 29 | 30 | image = new BitmapDrawable(getApplication().getResources(), bitmapResized); 31 | 32 | return image; 33 | } 34 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/EncryptionExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | import androidx.annotation.Nullable; 6 | 7 | import java.security.MessageDigest; 8 | import java.security.NoSuchAlgorithmException; 9 | 10 | /** 11 | * Created by Nyaruhodo on 07.05.2016. 12 | */ 13 | public class EncryptionExtensions { 14 | 15 | private EncryptionExtensions() throws IllegalAccessException { 16 | throw new IllegalAccessException(); 17 | } 18 | 19 | @NonNull 20 | public static String md5(@Nullable final String s) { 21 | try { 22 | // Create MD5 Hash 23 | MessageDigest digest = MessageDigest.getInstance("MD5"); 24 | digest.update(s.getBytes()); 25 | byte[] messageDigest = digest.digest(); 26 | 27 | // Create Hex String 28 | StringBuilder hexString = new StringBuilder(); 29 | for (int i = 0; i < messageDigest.length; i++) 30 | hexString.append(Integer.toHexString(0xFF & messageDigest[i])); 31 | return hexString.toString(); 32 | 33 | } catch (NoSuchAlgorithmException e) { 34 | e.printStackTrace(); 35 | } 36 | return ""; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/FontExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | import android.graphics.Typeface; 4 | import android.view.View; 5 | import android.widget.TextView; 6 | 7 | import androidx.annotation.ColorRes; 8 | import androidx.annotation.NonNull; 9 | 10 | import java.util.Collection; 11 | 12 | import static com.exozet.android.core.utils.ViewExtensions.setColor; 13 | 14 | /** 15 | * Created by jan.rabe on 17/12/15. 16 | */ 17 | final public class FontExtensions { 18 | 19 | private FontExtensions() throws IllegalAccessException { 20 | throw new IllegalAccessException(); 21 | } 22 | 23 | public static void applyFont(@NonNull final Typeface font, @NonNull final TextView... views) { 24 | for (final TextView view : views) 25 | view.setTypeface(font); 26 | } 27 | 28 | public static void applyColor(@ColorRes final int resourceId, @NonNull final Collection views) { 29 | for (final View v : views) 30 | setColor(v, resourceId); 31 | } 32 | 33 | public static void applyColor(@ColorRes final int resourceId, @NonNull final View... views) { 34 | for (final View v : views) 35 | setColor(v, resourceId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/JUnitExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by jan.rabe on 10/08/16. 8 | */ 9 | 10 | public class JUnitExtensions { 11 | 12 | private JUnitExtensions() throws IllegalAccessException { 13 | throw new IllegalAccessException(); 14 | } 15 | 16 | public static boolean isJUnitTest() { 17 | StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 18 | StackTraceElement[] list = stackTrace; 19 | for (StackTraceElement element : list) { 20 | if (element.getClassName().startsWith("org.junit.")) { 21 | return true; 22 | } 23 | } 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/KeyGuardExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.KeyguardManager; 5 | import android.content.Context; 6 | import android.os.PowerManager; 7 | 8 | import androidx.annotation.RequiresPermission; 9 | 10 | import static android.Manifest.permission.DISABLE_KEYGUARD; 11 | 12 | public class KeyGuardExtensions { 13 | 14 | @RequiresPermission(DISABLE_KEYGUARD) 15 | public static void unlockScreen(Context context) { 16 | KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); 17 | final KeyguardManager.KeyguardLock kl = km.newKeyguardLock("MyKeyguardLock"); 18 | kl.disableKeyguard(); 19 | 20 | PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 21 | @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK 22 | | PowerManager.ACQUIRE_CAUSES_WAKEUP 23 | | PowerManager.ON_AFTER_RELEASE, "MyWakeLock"); 24 | wakeLock.acquire(10 * 60 * 1000L /*10 minutes*/); 25 | } 26 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/ShellExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils 2 | 3 | import android.text.TextUtils 4 | import android.util.Log 5 | import net.kibotu.logger.Logger 6 | import org.apache.commons.io.IOUtils 7 | 8 | /** 9 | * Created by [Jan Rabe](https://about.me/janrabe). 10 | */ 11 | 12 | 13 | fun executeShellCommand(command: String): Boolean { 14 | var process: Process? = null 15 | try { 16 | Logger.v("Command", "> $command") 17 | process = Runtime.getRuntime().exec(command) 18 | 19 | val outputStreamResult = IOUtils.toString(process.errorStream) 20 | if (!TextUtils.isEmpty(outputStreamResult)) { 21 | Logger.e("Command", "> outputStreamResult") 22 | Logger.snackbar(outputStreamResult) 23 | return false 24 | } 25 | 26 | val inputStreamResult = IOUtils.toString(process.inputStream) 27 | if (!TextUtils.isEmpty(inputStreamResult)) { 28 | Logger.v("Command", "> $inputStreamResult") 29 | Logger.snackbar(inputStreamResult) 30 | } 31 | 32 | return true 33 | } catch (e: Exception) { 34 | Log.e("Command", "" + e.message) 35 | return false 36 | } finally { 37 | if (process != null) { 38 | try { 39 | process.destroy() 40 | } catch (e: Exception) { 41 | Log.e("Command", "" + e.message) 42 | } 43 | 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /core/src/main/java/com/exozet/android/core/utils/ThreadExtensions.java: -------------------------------------------------------------------------------- 1 | package com.exozet.android.core.utils; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import net.kibotu.logger.Logger; 6 | 7 | 8 | /** 9 | * Created by Jan Rabe on 22/10/15. 10 | */ 11 | final public class ThreadExtensions { 12 | 13 | private static final String TAG = ThreadExtensions.class.getSimpleName(); 14 | 15 | private ThreadExtensions() throws IllegalAccessException { 16 | throw new IllegalAccessException(); 17 | } 18 | 19 | public static void setThreadPriority(final int priority) { 20 | Logger.v(TAG, "From thread priority: " + android.os.Process.getThreadPriority(android.os.Process.myTid())); 21 | android.os.Process.setThreadPriority(priority); 22 | Logger.v(TAG, "To thread priority: " + android.os.Process.getThreadPriority(android.os.Process.myTid())); 23 | } 24 | 25 | public static void safeSleep(final int millisecond) { 26 | try { 27 | Thread.sleep(millisecond); 28 | } catch (final InterruptedException e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | public static void startNewThread(@NonNull final Runnable runnable) { 34 | new Thread(runnable).start(); 35 | } 36 | } -------------------------------------------------------------------------------- /core/src/main/res/anim/custom_enter_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /core/src/main/res/anim/custom_enter_from_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 34 | -------------------------------------------------------------------------------- /core/src/main/res/anim/custom_exit_to_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 11 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /core/src/main/res/anim/custom_exit_to_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /core/src/main/res/anim/enter_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | -------------------------------------------------------------------------------- /core/src/main/res/anim/enter_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/anim/enter_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/anim/exit_to_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | -------------------------------------------------------------------------------- /core/src/main/res/anim/exit_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 16 | -------------------------------------------------------------------------------- /core/src/main/res/anim/exit_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 16 | -------------------------------------------------------------------------------- /core/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /core/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /core/src/main/res/anim/grow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /core/src/main/res/anim/pop_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 19 | -------------------------------------------------------------------------------- /core/src/main/res/anim/pop_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 17 | -------------------------------------------------------------------------------- /core/src/main/res/anim/shake.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/main/res/anim/shrink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 17 | -------------------------------------------------------------------------------- /core/src/main/res/anim/side_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/main/res/anim/side_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_enter_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_exit_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_in_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_out_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_pop_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /core/src/main/res/anim/slide_pop_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /core/src/main/res/animator/alpha_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /core/src/main/res/animator/alpha_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /core/src/main/res/animator/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/main/res/animator/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/main/res/animator/flip_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 19 | 20 | 26 | 27 | 31 | 32 | 36 | -------------------------------------------------------------------------------- /core/src/main/res/animator/raise.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 21 | 25 | 26 | 27 | 31 | 32 | -------------------------------------------------------------------------------- /core/src/main/res/animator/shrink_scale_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /core/src/main/res/layout/fragment_markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | -------------------------------------------------------------------------------- /core/src/main/res/layout/fragment_raw_output.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 32 | 33 | 34 | 35 | 44 | 45 | -------------------------------------------------------------------------------- /core/src/main/res/transition/move.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/values-ldltr/ltr-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0 4 | 1 5 | -1 6 | false 7 | -------------------------------------------------------------------------------- /core/src/main/res/values-ldrtl/rtl-config.xml: -------------------------------------------------------------------------------- 1 | 2 | true 3 | -1 4 | 1 5 | 180 6 | -------------------------------------------------------------------------------- /core/src/main/res/values-sw600dp/tablet-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | -------------------------------------------------------------------------------- /core/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /core/src/main/res/values-v21/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /core/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /core/src/main/res/values/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | false 8 | false 9 | false 10 | true 11 | 12 | exozet 13 | exozet 14 | 15 | 16 | 17 | 250 18 | 750 19 | 20 | -------------------------------------------------------------------------------- /core/src/main/res/values/debug.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Section 7 | 8 | 9 | 10 | 11 | Reset App 12 | Restart App 13 | Toggle Logging RAM 14 | Log Current Threads 15 | Log Current Backstack 16 | Log Current Fragment 17 | 18 | Trigger Local Push 19 | 20 | 21 | 22 | Splash Screen 23 | README.md 24 | CHANGELOG.md 25 | Build Info 26 | 27 | 28 | 29 | Default 30 | Korean 31 | 32 | Device is online. 33 | Device is not online. 34 | 35 | -------------------------------------------------------------------------------- /core/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 16dp 7 | 8 | 16dp 9 | 24dp 10 | 32dp 11 | 48dp 12 | 56dp 13 | 14 | 112sp 15 | 70sp 16 | 56sp 17 | 45sp 18 | 34sp 19 | 24sp 20 | 20sp 21 | 18sp 22 | 16sp 23 | 14sp 24 | 12sp 25 | 26 | 4dp 27 | 6dp 28 | 6dp 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /core/src/main/res/values/fonts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sans-serif-light 6 | sans-serif-medium 7 | sans-serif 8 | sans-serif-condensed 9 | sans-serif-black 10 | sans-serif-thin 11 | 12 | 13 | -------------------------------------------------------------------------------- /core/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 120 6 | 7 | 300 8 | 2200 9 | 10 | 400 11 | 12 | 100 13 | 14 | 1500 15 | 500 16 | 300 17 | 18 | -------------------------------------------------------------------------------- /core/src/main/res/values/ltr-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 0 3 | 1 4 | -1 5 | false 6 | 7 | -------------------------------------------------------------------------------- /core/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share with: 5 | 6 | -------------------------------------------------------------------------------- /core/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 31 | 32 | -------------------------------------------------------------------------------- /core/src/main/res/values/transition_names.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | toolbar_transition 6 | 7 | -------------------------------------------------------------------------------- /core/src/main/res/xml/backup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | -------------------------------------------------------------------------------- /debug.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/debug.jks -------------------------------------------------------------------------------- /documentation/AmazonFireTvRemoteControlListener.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/documentation/AmazonFireTvRemoteControlListener.jpg -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # gralde config 2 | org.gradle.jvmargs=-Xms8g 3 | org.gradle.daemon=true 4 | org.gradle.parallel=true 5 | org.gradle.workers.max=32 6 | org.gradle.caching=true 7 | 8 | # kotlin config 9 | #android.enableAapt2=false 10 | kotlinOptions.allWarningsAsErrors=false 11 | android.enableR8=true 12 | android.enableR8.fullMode=true 13 | android.enableD8=true 14 | android.enableD8.desugaring=true 15 | # https://developer.android.com/topic/libraries/support-library/androidx-overview 16 | android.useAndroidX=true 17 | # Automatically convert third-party libraries to use AndroidX 18 | android.enableJetifier=true 19 | # Kotlin code style for this project: "official" or "obsolete": 20 | kotlin.code.style=official 21 | 22 | #robolectric 23 | android.enableUnitTestBinaryResources=true 24 | # https://speakerdeck.com/snehpandya18/mastering-gradle-3 25 | # gradle=build -x lint -x lintVitalRelease 26 | # https://developer.android.com/studio/preview/features/?utm_source=android-studio#lazy_task_config 27 | android.debug.obsoleteApi=false 28 | #android.proguard.enableRulesExtraction=false 29 | 30 | kapt.incremental.apt=true 31 | android.enableSeparateAnnotationProcessing=true 32 | javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true 33 | 34 | # app 35 | APPLICATION_ID=com.exozet.android.core.demo 36 | VERSION_NAME=1.0.0 37 | 38 | # debug keystore 39 | DEBUG_KEYSYORE_PATH=../debug.jks 40 | DEBUG_KEYSTORE_ALLIAS=exozet 41 | DEBUG_STORE_PASSWORD=Ex0zet!337 42 | DEBUG_KEY_PASSWORD=Ex0zet!337 43 | # AE:98:A8:92:E2:82:18:24:58:BB:7D:2D:3D:36:6D:13:38:1F:08:01 44 | 45 | # release keystore 46 | RELEASE_KEYSYORE_PATH=../release.jks 47 | RELEASE_KEYSTORE_ALIAS=exozet 48 | RELEASE_STORE_PASSWORD=Ex0zet!337 49 | RELEASE_KEY_PASSWORD=Ex0zet!337 50 | # 77:16:5A:50:63:37:6D:1F:79:D4:F2:54:A4:EA:28:6E:8A:FE:74:82 51 | 52 | # VSC config 53 | VSC_PATH=https://github.com/exozet/AndroidCore.git 54 | 55 | # github, gitlab, bitbucket 56 | VSC_TYPE=github -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | before_install: 4 | - curl https://gist.githubusercontent.com/kibotu/2485f0f88df159c6bc215ab9946e23e3/raw/28334f15bb6641667966b1732d8d8ef86dc1afb3/android-sdk-licenses.sh | sh -------------------------------------------------------------------------------- /release.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kibotu/AndroidCore/72cbb02bc262b1c26450b570e41842ab65406c3e/release.jks -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':core' 2 | --------------------------------------------------------------------------------