├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── co │ │ └── garmax │ │ └── materialflashlight │ │ ├── CoreApplication.kt │ │ ├── di │ │ ├── DataModule.kt │ │ └── PresentationModule.kt │ │ ├── extensions │ │ └── Android.kt │ │ ├── features │ │ ├── LightManager.kt │ │ ├── modes │ │ │ ├── IntervalStrobeMode.kt │ │ │ ├── ModeBase.kt │ │ │ ├── ModeFactory.kt │ │ │ ├── SosMode.kt │ │ │ ├── SoundStrobeMode.kt │ │ │ └── TorchMode.kt │ │ └── modules │ │ │ ├── BaseCameraFlashModule.kt │ │ │ ├── CameraFlashModuleV16.kt │ │ │ ├── CameraFlashModuleV23.kt │ │ │ ├── ModuleBase.kt │ │ │ ├── ModuleFactory.kt │ │ │ └── ScreenModule.kt │ │ ├── repositories │ │ └── SettingsRepository.kt │ │ ├── service │ │ ├── ForegroundService.kt │ │ └── LightTileService.kt │ │ ├── ui │ │ ├── BaseFragment.kt │ │ ├── PermissionsActivity.kt │ │ ├── light │ │ │ └── LightFragment.kt │ │ ├── main │ │ │ ├── MainFragment.kt │ │ │ └── MainViewModel.kt │ │ ├── root │ │ │ ├── RootActivity.kt │ │ │ └── RootViewModel.kt │ │ └── views │ │ │ └── MaskedScrollView.kt │ │ ├── utils │ │ └── PostExecutionThread.kt │ │ └── widget │ │ ├── WidgetManager.kt │ │ └── WidgetProviderButton.kt │ └── res │ ├── animator │ ├── appbar_background_day.xml │ ├── appbar_background_night.xml │ ├── appbar_clouds_day.xml │ ├── appbar_clouds_night.xml │ ├── appbar_grass_day.xml │ ├── appbar_grass_night.xml │ ├── appbar_hide.xml │ ├── appbar_show.xml │ ├── appbar_stars_day.xml │ └── appbar_stars_night.xml │ ├── drawable-hdpi │ └── ic_light_notification.png │ ├── drawable-mdpi │ └── ic_light_notification.png │ ├── drawable-nodpi │ └── ic_widget_button_preview.png │ ├── drawable-xhdpi │ └── ic_light_notification.png │ ├── drawable-xxhdpi │ └── ic_light_notification.png │ ├── drawable-xxxhdpi │ └── ic_light_notification.png │ ├── drawable │ ├── avc_appbar_day.xml │ ├── avc_appbar_night.xml │ ├── bg_rounded.xml │ ├── bg_status_bar_gradient.xml │ ├── ic_power_off.xml │ ├── ic_power_on.xml │ ├── ic_quick_settings_active.xml │ ├── ic_quick_settings_inactive.xml │ ├── ic_quick_settings_unavailable.xml │ ├── ic_widget_button_off.xml │ ├── ic_widget_button_on.xml │ ├── vc_appbar_day.xml │ └── vc_appbar_night.xml │ ├── layout │ ├── activity_root.xml │ ├── fragment_light.xml │ ├── fragment_main.xml │ └── view_widget_button.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── mipmap-hdpi-v26 │ └── ic_launcher_foreground.png │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi-v26 │ └── ic_launcher_foreground.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi-v26 │ └── ic_launcher_foreground.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi-v26 │ └── ic_launcher_foreground.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi-v26 │ └── ic_launcher_foreground.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-ru │ └── strings.xml │ ├── values-v19 │ └── styles.xml │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── integer.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── widget_info_button.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | /sign 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Material Flashlight 2 | ======== 3 | 4 | Flashlight application for android with some cool features 5 | 6 | Download 7 | -------- 8 | 9 | Google Play [Material Flashlight][googleplaylink] 10 | 11 | F-Droid [Material Flashlight][fdroidlink] 12 | 13 | License 14 | ======= 15 | 16 | Licensed under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. 27 | 28 | 29 | [googleplaylink]: https://play.google.com/store/apps/details?id=co.garmax.materialflashlight 30 | [fdroidlink]: https://f-droid.org/packages/co.garmax.materialflashlight/ 31 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 31 8 | 9 | defaultConfig { 10 | applicationId "co.garmax.materialflashlight" 11 | minSdkVersion 16 12 | targetSdkVersion 31 13 | versionCode 30 14 | versionName "2.8" 15 | vectorDrawables.useSupportLibrary = true 16 | resConfigs "en", "ru" 17 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 18 | multiDexEnabled true 19 | } 20 | 21 | signingConfigs { 22 | release { 23 | def props = new Properties() 24 | props.load(new FileInputStream(rootProject.file('./../signs/garmax.pwd'))) 25 | 26 | storeFile rootProject.file(props.keyStore) 27 | keyAlias props.keyStorePassword 28 | storePassword props.keyAlias 29 | keyPassword props.keyAliasPassword 30 | } 31 | } 32 | 33 | buildTypes { 34 | release { 35 | signingConfig signingConfigs.release 36 | minifyEnabled true 37 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 38 | } 39 | } 40 | 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_11 43 | targetCompatibility JavaVersion.VERSION_11 44 | } 45 | 46 | kotlinOptions { 47 | jvmTarget = "11" 48 | } 49 | 50 | buildFeatures { 51 | viewBinding true 52 | } 53 | } 54 | 55 | dependencies { 56 | implementation fileTree(dir: 'libs', include: ['*.jar']) 57 | 58 | implementation 'com.google.android.material:material:1.6.1' 59 | 60 | // AndroidX 61 | implementation 'androidx.vectordrawable:vectordrawable:1.1.0' 62 | implementation 'androidx.appcompat:appcompat:1.4.2' 63 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 64 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' 65 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' 66 | implementation 'androidx.fragment:fragment-ktx:1.4.1' 67 | 68 | // DI 69 | api 'io.insert-koin:koin-core:3.1.2' 70 | api 'io.insert-koin:koin-android:3.1.2' 71 | 72 | // rx 73 | implementation 'io.reactivex.rxjava2:rxjava:2.2.10' 74 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 75 | 76 | // debug 77 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' 78 | 79 | // utils 80 | implementation 'com.jakewharton.timber:timber:5.0.1' 81 | 82 | // test 83 | testImplementation 'junit:junit:4.13.2' 84 | testImplementation 'org.mockito:mockito-core:3.3.3' 85 | } 86 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 19 | 20 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 45 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/CoreApplication.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight 2 | 3 | import android.app.Application 4 | import co.garmax.materialflashlight.di.dataModule 5 | import co.garmax.materialflashlight.di.presentationModule 6 | import org.koin.android.ext.koin.androidContext 7 | import org.koin.core.context.startKoin 8 | import timber.log.Timber 9 | import timber.log.Timber.DebugTree 10 | 11 | class CoreApplication : Application() { 12 | override fun onCreate() { 13 | super.onCreate() 14 | 15 | startKoin { 16 | androidContext(this@CoreApplication) 17 | modules(presentationModule, dataModule) 18 | } 19 | 20 | // Logs 21 | if (BuildConfig.DEBUG) { 22 | Timber.plant(DebugTree()) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/di/DataModule.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.di 2 | 3 | import co.garmax.materialflashlight.features.LightManager 4 | import co.garmax.materialflashlight.features.modes.ModeFactory 5 | import co.garmax.materialflashlight.features.modules.ModuleFactory 6 | import co.garmax.materialflashlight.repositories.SettingsRepository 7 | import co.garmax.materialflashlight.utils.PostExecutionThread 8 | import co.garmax.materialflashlight.widget.WidgetManager 9 | import io.reactivex.android.schedulers.AndroidSchedulers 10 | import io.reactivex.schedulers.Schedulers 11 | import org.koin.android.ext.koin.androidContext 12 | import org.koin.dsl.module 13 | 14 | val dataModule = module { 15 | single { 16 | object : PostExecutionThread { 17 | override val scheduler get() = AndroidSchedulers.mainThread() 18 | } 19 | } 20 | single { Schedulers.io() } 21 | 22 | single { 23 | LightManager(get(), androidContext()).apply { 24 | val settingsRepository: SettingsRepository = get() 25 | val moduleFactory: ModuleFactory = get() 26 | val modeFactory: ModeFactory = get() 27 | 28 | setModule(moduleFactory.getModule(settingsRepository.module)) 29 | setMode(modeFactory.getMode(settingsRepository.mode)) 30 | } 31 | } 32 | 33 | single { SettingsRepository(get()) } 34 | single { WidgetManager(androidContext()) } 35 | single { ModuleFactory(androidContext()) } 36 | single { ModeFactory(get(), androidContext()) } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/di/PresentationModule.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.di 2 | 3 | import co.garmax.materialflashlight.ui.main.MainViewModel 4 | import co.garmax.materialflashlight.ui.root.RootViewModel 5 | import org.koin.androidx.viewmodel.dsl.viewModel 6 | import org.koin.dsl.module 7 | 8 | val presentationModule = module { 9 | viewModel { RootViewModel(get(), get(), get()) } 10 | viewModel { MainViewModel(get(), get(), get(), get(), get()) } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/extensions/Android.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.extensions 2 | 3 | import android.content.res.Resources 4 | import androidx.fragment.app.Fragment 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.LiveData 7 | import androidx.lifecycle.MutableLiveData 8 | import androidx.lifecycle.Observer 9 | 10 | val Int.asDp get() = (this * Resources.getSystem().displayMetrics.density).toInt() 11 | 12 | fun liveDataOf(defValue: T? = null) = MutableLiveData().apply { 13 | defValue?.apply { value = this } 14 | } 15 | 16 | fun > LifecycleOwner.observe(liveData: L, body: (T?) -> Unit) = 17 | liveData.observe( 18 | when { 19 | this is Fragment && view != null -> viewLifecycleOwner 20 | else -> this 21 | }, 22 | Observer(body) 23 | ) 24 | 25 | fun > LifecycleOwner.observeNotNull(liveData: L, body: (T) -> Unit) = 26 | liveData.observe( 27 | when { 28 | this is Fragment && view != null -> viewLifecycleOwner 29 | else -> this 30 | }, 31 | { it?.let(body) } 32 | ) -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/LightManager.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | import co.garmax.materialflashlight.R 6 | import co.garmax.materialflashlight.features.modes.IntervalStrobeMode 7 | import co.garmax.materialflashlight.features.modes.ModeBase 8 | import co.garmax.materialflashlight.features.modules.ModuleBase 9 | import co.garmax.materialflashlight.widget.WidgetManager 10 | import io.reactivex.Observable 11 | import io.reactivex.disposables.Disposable 12 | import io.reactivex.subjects.BehaviorSubject 13 | 14 | class LightManager( 15 | private val widgetManager: WidgetManager, 16 | private val context: Context 17 | ) { 18 | 19 | val isTurnedOn get() = _toggleStateObservable.value == true 20 | 21 | val isSupported get() = requireModule().isSupported 22 | 23 | val toggleStateStream: Observable get() = _toggleStateObservable 24 | private val _toggleStateObservable = BehaviorSubject.createDefault(false) 25 | 26 | private var disposableModeState: Disposable? = null 27 | 28 | private var currentModule: ModuleBase? = null 29 | 30 | private var currentMode: ModeBase? = null 31 | 32 | fun turnOn() { 33 | if (isTurnedOn) return 34 | 35 | // Check that the module is supported 36 | if (!requireModule().isSupported) { 37 | Toast.makeText(context, R.string.toast_module_not_supported, Toast.LENGTH_LONG).show() 38 | return 39 | } 40 | 41 | // Check that the module is available 42 | if (!requireModule().isAvailable) { 43 | Toast.makeText(context, R.string.toast_module_not_available, Toast.LENGTH_LONG).show() 44 | return 45 | } 46 | 47 | // Check tht we have all permission for module and mode 48 | if (!requireModule().checkPermissions() || !requireMode().checkPermissions()) { 49 | return 50 | } 51 | 52 | requireModule().init() 53 | 54 | // Listen mode light state and set to module 55 | disposableModeState = requireMode() 56 | .lightVolumeSubject 57 | .subscribe { 58 | requireModule().setBrightness(it) 59 | } 60 | 61 | requireMode().start() 62 | 63 | _toggleStateObservable.onNext(true) 64 | 65 | widgetManager.updateWidgets() 66 | } 67 | 68 | fun turnOff() { 69 | if (!isTurnedOn) return 70 | 71 | requireMode().stop() 72 | requireModule().release() 73 | requireModule().release() 74 | _toggleStateObservable.onNext(false) 75 | 76 | // Free observable 77 | disposableModeState?.dispose() 78 | widgetManager.updateWidgets() 79 | } 80 | 81 | private fun requireModule(): ModuleBase { 82 | return currentModule ?: throw IllegalStateException("Module not set") 83 | } 84 | 85 | private fun requireMode(): ModeBase { 86 | return currentMode ?: throw IllegalStateException("Mode not set") 87 | } 88 | 89 | fun setMode(mode: ModeBase?) { 90 | val isWasTurnedOn = isTurnedOn 91 | turnOff() 92 | currentMode = mode 93 | 94 | if (isWasTurnedOn) turnOn() 95 | } 96 | 97 | fun setModule(module: ModuleBase?) { 98 | val isWasTurnedOn = isTurnedOn 99 | turnOff() 100 | currentModule = module 101 | 102 | if (isWasTurnedOn) turnOn() 103 | } 104 | 105 | fun setStrobePeriod(timeOn: Int, timeOff: Int) { 106 | if (currentMode is IntervalStrobeMode) { 107 | (currentMode as IntervalStrobeMode).updateStrobe(timeOn, timeOff) 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modes/IntervalStrobeMode.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modes 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.Scheduler 5 | import io.reactivex.disposables.Disposable 6 | import java.util.concurrent.TimeUnit 7 | 8 | /** 9 | * Interrupted light with equal interval 10 | */ 11 | class IntervalStrobeMode(private val workerScheduler: Scheduler) : ModeBase() { 12 | private var disposable: Disposable? = null 13 | 14 | override fun start() { 15 | disposable = Observable.interval( 16 | 0, 17 | (STROBE_PERIOD + DELAY_PERIOD).toLong(), 18 | TimeUnit.MILLISECONDS, 19 | workerScheduler 20 | ) 21 | .doOnNext { any: Long? -> setBrightness(MAX_LIGHT_VOLUME) } 22 | .delay(STROBE_PERIOD.toLong(), TimeUnit.MILLISECONDS) 23 | .doOnNext { any: Long? -> setBrightness(MIN_LIGHT_VOLUME) } 24 | .delay(DELAY_PERIOD.toLong(), TimeUnit.MILLISECONDS) 25 | .subscribe { any: Long? -> } 26 | } 27 | 28 | override fun stop() { 29 | setBrightness(MIN_LIGHT_VOLUME) 30 | disposable?.dispose() 31 | disposable = null 32 | } 33 | 34 | override fun checkPermissions(): Boolean { 35 | return true 36 | } 37 | 38 | fun updateStrobe(timeOn: Int, timeOff: Int) { 39 | STROBE_PERIOD = if (timeOn <= 0) DEFAULT_STROBE_PERIOD else timeOn 40 | DELAY_PERIOD = if (timeOff <= 0) DEFAULT_DELAY_PERIOD else timeOff 41 | 42 | if (disposable != null) { 43 | stop() 44 | start() 45 | } 46 | } 47 | 48 | companion object { 49 | const val DEFAULT_STROBE_PERIOD = 300 50 | const val DEFAULT_DELAY_PERIOD = 200 51 | 52 | var STROBE_PERIOD = DEFAULT_STROBE_PERIOD 53 | var DELAY_PERIOD = DEFAULT_DELAY_PERIOD 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modes/ModeBase.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modes 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.subjects.PublishSubject 5 | import io.reactivex.subjects.Subject 6 | 7 | /** 8 | * Module generate event for light volume according to implementation 9 | * (torch, interval, microphone volume) 10 | */ 11 | abstract class ModeBase { 12 | 13 | enum class Mode { 14 | MODE_SOUND_STROBE, MODE_INTERVAL_STROBE, MODE_TORCH, MODE_SOS 15 | } 16 | 17 | /** 18 | * Volume of the light 19 | */ 20 | val lightVolumeSubject: Observable get() = _lightVolumeSubject 21 | private val _lightVolumeSubject: Subject = PublishSubject.create() 22 | 23 | /** 24 | * Start mode. Light will be turned on\off depends on mode implementation. 25 | */ 26 | abstract fun start() 27 | 28 | /** 29 | * Stop mode. Light will be turned off. 30 | */ 31 | abstract fun stop() 32 | 33 | /** 34 | * Check runtime permission for the mode 35 | * @return true if all needed permission granted, false - if permission requested 36 | */ 37 | abstract fun checkPermissions(): Boolean 38 | 39 | /** 40 | * Change brightnessObservable state 41 | */ 42 | fun setBrightness(percentage: Int) { 43 | _lightVolumeSubject.onNext(percentage) 44 | } 45 | 46 | companion object { 47 | const val MAX_LIGHT_VOLUME = 100 48 | const val MIN_LIGHT_VOLUME = 0 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modes/ModeFactory.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modes 2 | 3 | import android.content.Context 4 | import co.garmax.materialflashlight.features.modes.ModeBase 5 | import io.reactivex.Scheduler 6 | 7 | class ModeFactory(private val workerScheduler: Scheduler, private val context: Context) { 8 | 9 | fun getMode(mode: ModeBase.Mode): ModeBase { 10 | if (mode === ModeBase.Mode.MODE_INTERVAL_STROBE) { 11 | return IntervalStrobeMode(workerScheduler) 12 | } else if (mode === ModeBase.Mode.MODE_SOS) { 13 | return SosMode(workerScheduler) 14 | } else if (mode === ModeBase.Mode.MODE_SOUND_STROBE) { 15 | return SoundStrobeMode(context, workerScheduler) 16 | } else if (mode === ModeBase.Mode.MODE_TORCH) { 17 | return TorchMode() 18 | } 19 | throw IllegalArgumentException("$mode mode not implemented") 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modes/SosMode.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modes 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.Scheduler 5 | import io.reactivex.disposables.Disposable 6 | import java.util.concurrent.TimeUnit 7 | 8 | /** 9 | * Implement SOS 10 | */ 11 | class SosMode(private val workerScheduler: Scheduler) : ModeBase() { 12 | 13 | private var disposableInterval: Disposable? = null 14 | 15 | override fun start() { 16 | disposableInterval = Observable.interval( 17 | 0, 18 | SOS_PERIOD.toLong(), 19 | TimeUnit.MILLISECONDS, 20 | workerScheduler 21 | ) 22 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 23 | .delay(STROBE_SHORT.toLong(), TimeUnit.MILLISECONDS) 24 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 short 25 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 26 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 27 | .delay(STROBE_SHORT.toLong(), TimeUnit.MILLISECONDS) 28 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 short 29 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 30 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 31 | .delay(STROBE_SHORT.toLong(), TimeUnit.MILLISECONDS) 32 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 short 33 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 34 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 35 | .delay(STROBE_LONG.toLong(), TimeUnit.MILLISECONDS) 36 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 long 37 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 38 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 39 | .delay(STROBE_LONG.toLong(), TimeUnit.MILLISECONDS) 40 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 long 41 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 42 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 43 | .delay(STROBE_LONG.toLong(), TimeUnit.MILLISECONDS) 44 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 long 45 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 46 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 47 | .delay(STROBE_SHORT.toLong(), TimeUnit.MILLISECONDS) 48 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 short 49 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 50 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 51 | .delay(STROBE_SHORT.toLong(), TimeUnit.MILLISECONDS) 52 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 short 53 | .delay(DELAY_SHORT.toLong(), TimeUnit.MILLISECONDS) 54 | .doOnNext { setBrightness(MAX_LIGHT_VOLUME) } 55 | .delay(STROBE_SHORT.toLong(), TimeUnit.MILLISECONDS) 56 | .doOnNext { setBrightness(MIN_LIGHT_VOLUME) } // 1 short 57 | .delay(DELAY_LONG.toLong(), TimeUnit.MILLISECONDS) 58 | .subscribe { } 59 | } 60 | 61 | override fun stop() { 62 | setBrightness(MIN_LIGHT_VOLUME) 63 | 64 | disposableInterval?.dispose() 65 | } 66 | 67 | override fun checkPermissions(): Boolean { 68 | return true 69 | } 70 | 71 | companion object { 72 | private const val STROBE_SHORT = 400 73 | private const val STROBE_LONG = 900 74 | private const val DELAY_SHORT = 400 75 | private const val DELAY_LONG = 2500 76 | private const val SOS_PERIOD = 77 | (STROBE_SHORT * 3 + DELAY_SHORT * 3) * 2 + 2 * DELAY_SHORT + 3 * STROBE_LONG + DELAY_LONG 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modes/SoundStrobeMode.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modes 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.media.AudioFormat 7 | import android.media.AudioRecord 8 | import android.media.MediaRecorder 9 | import androidx.core.content.ContextCompat 10 | import co.garmax.materialflashlight.ui.PermissionsActivity 11 | import io.reactivex.Observable 12 | import io.reactivex.Scheduler 13 | import io.reactivex.disposables.Disposable 14 | import timber.log.Timber 15 | import java.util.concurrent.TimeUnit 16 | import kotlin.math.max 17 | import kotlin.math.min 18 | 19 | /** 20 | * Interrupted light depending on the around noise volume 21 | */ 22 | class SoundStrobeMode( 23 | private val context: Context, 24 | private val workerScheduler: Scheduler 25 | ) : ModeBase() { 26 | 27 | private var disposableInterval: Disposable? = null 28 | 29 | // Audio staff 30 | private var audioRecord: AudioRecord? = null 31 | private var bufferSize = 0 32 | private var maxAmplitude = 0 33 | private var minAmplitude = 0 34 | 35 | override fun checkPermissions(): Boolean { 36 | if (ContextCompat.checkSelfPermission( 37 | context, 38 | Manifest.permission.RECORD_AUDIO 39 | ) != PackageManager.PERMISSION_GRANTED 40 | ) { 41 | PermissionsActivity.startActivity(context, arrayOf(Manifest.permission.RECORD_AUDIO)) 42 | return false 43 | } 44 | return true 45 | } 46 | 47 | override fun start() { 48 | bufferSize = AudioRecord.getMinBufferSize( 49 | 8000, AudioFormat.CHANNEL_IN_MONO, 50 | AudioFormat.ENCODING_PCM_16BIT 51 | ) 52 | audioRecord = AudioRecord( 53 | MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, 54 | AudioFormat.ENCODING_PCM_16BIT, bufferSize 55 | ).apply { 56 | startRecording() 57 | } 58 | minAmplitude = 0 59 | maxAmplitude = 0 60 | disposableInterval = Observable.interval( 61 | 0, 62 | CHECK_AMPLITUDE_PERIOD, 63 | TimeUnit.MILLISECONDS, 64 | workerScheduler 65 | ) 66 | .subscribe { _: Long? -> setBrightness(amplitudePercentage(amplitude)) } 67 | 68 | setBrightness(MAX_AMPLITUDE) 69 | } 70 | 71 | override fun stop() { 72 | setBrightness(MIN_LIGHT_VOLUME) 73 | disposableInterval?.dispose() 74 | 75 | audioRecord?.stop() 76 | } 77 | 78 | private val amplitude: Int 79 | get() { 80 | val buffer = ShortArray(bufferSize) 81 | audioRecord?.read(buffer, 0, bufferSize) 82 | var max: Short = 0 83 | for (s in buffer) { 84 | if (s > max) { 85 | max = s 86 | } 87 | } 88 | return max.toInt() 89 | } 90 | 91 | private fun amplitudePercentage(curAmplitude: Int): Int { 92 | // Reduce amplitude min\max tunnel 93 | // because min\max value can be above or below initial avg value 94 | // and limit with 0 and max amplitude value 95 | maxAmplitude = max(maxAmplitude - INCREASE_STEP, 0) 96 | minAmplitude = min(minAmplitude + INCREASE_STEP, MAX_AMPLITUDE) 97 | 98 | // Save min max values 99 | maxAmplitude = max(maxAmplitude, curAmplitude) 100 | minAmplitude = min(minAmplitude, curAmplitude) 101 | 102 | // If min and max equal, exit to prevent dividing by zero 103 | if (minAmplitude == maxAmplitude) { 104 | return 0 105 | } 106 | 107 | // Calculate percentage of current amplitude of difference max and min amplitude 108 | val avgAmplitude = (curAmplitude - minAmplitude) * 100 / (maxAmplitude - minAmplitude) 109 | Timber.d("Sound amplitude min: $minAmplitude, max: $maxAmplitude, cur: $curAmplitude; avg: $avgAmplitude") 110 | return avgAmplitude 111 | } 112 | 113 | companion object { 114 | private const val CHECK_AMPLITUDE_PERIOD = 50L 115 | private const val INCREASE_STEP = 150 116 | private const val MAX_AMPLITUDE = 32767 117 | } 118 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modes/TorchMode.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modes 2 | 3 | /** 4 | * Just steady light 5 | */ 6 | class TorchMode : ModeBase() { 7 | 8 | override fun start() { 9 | setBrightness(MAX_LIGHT_VOLUME) 10 | } 11 | 12 | override fun stop() { 13 | setBrightness(MIN_LIGHT_VOLUME) 14 | } 15 | 16 | override fun checkPermissions(): Boolean { 17 | return true 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modules/BaseCameraFlashModule.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modules 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import androidx.core.content.ContextCompat 7 | import co.garmax.materialflashlight.ui.PermissionsActivity 8 | 9 | /** 10 | * Module for camera LED flashlight 11 | */ 12 | abstract class BaseCameraFlashModule(val context: Context) : ModuleBase { 13 | abstract fun lightOn() 14 | abstract fun lightOff() 15 | 16 | abstract override val isAvailable: Boolean 17 | abstract override val isSupported: Boolean 18 | 19 | override fun init() { 20 | //Do nothing 21 | } 22 | 23 | override fun setBrightness(percents: Int) { 24 | if (percents < 50) lightOff() else lightOn() 25 | } 26 | 27 | override fun checkPermissions(): Boolean { 28 | if (ContextCompat.checkSelfPermission( 29 | context, 30 | Manifest.permission.CAMERA 31 | ) != PackageManager.PERMISSION_GRANTED 32 | ) { 33 | PermissionsActivity.startActivity(context, arrayOf(Manifest.permission.CAMERA)) 34 | return false 35 | } 36 | 37 | return true 38 | } 39 | 40 | override fun release() { 41 | lightOff() 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modules/CameraFlashModuleV16.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modules 2 | 3 | import android.content.Context 4 | import android.graphics.SurfaceTexture 5 | import android.hardware.Camera 6 | import android.hardware.Camera.CameraInfo 7 | import timber.log.Timber 8 | import java.io.IOException 9 | 10 | /** 11 | * Module for camera LED flashlight 12 | */ 13 | class CameraFlashModuleV16(context: Context) : BaseCameraFlashModule(context) { 14 | 15 | override val isAvailable: Boolean 16 | get() { 17 | var result = false 18 | try { 19 | if (camera != null) { 20 | // Try to get parameters to check if instance is available 21 | camera!!.parameters 22 | result = true 23 | } 24 | } catch (e: Exception) { 25 | Timber.e(e, "Camera instance not available") 26 | } 27 | 28 | // Release camera if we have problem with it 29 | if (camera != null && !result) release() 30 | 31 | return result 32 | } 33 | 34 | override val isSupported 35 | get() = if (isAvailable) { 36 | val flashModes = camera?.parameters?.supportedFlashModes 37 | isParameterSupported( 38 | Camera.Parameters.FLASH_MODE_TORCH, 39 | flashModes 40 | ) || isParameterSupported(Camera.Parameters.FLASH_MODE_ON, flashModes) 41 | } else { 42 | true 43 | } 44 | 45 | private var camera: Camera? = null 46 | 47 | private var previewTexture: SurfaceTexture? = SurfaceTexture(0) 48 | 49 | init { 50 | initializeCamera() 51 | } 52 | 53 | override fun lightOn() { 54 | val params = camera?.parameters 55 | val flashModes = params?.supportedFlashModes 56 | if (isParameterSupported(Camera.Parameters.FLASH_MODE_TORCH, flashModes)) { 57 | params?.flashMode = Camera.Parameters.FLASH_MODE_TORCH 58 | } else if (isParameterSupported(Camera.Parameters.FLASH_MODE_ON, flashModes)) { 59 | params?.flashMode = Camera.Parameters.FLASH_MODE_ON 60 | } 61 | camera?.parameters = params 62 | camera?.startPreview() 63 | } 64 | 65 | override fun lightOff() { 66 | camera?.let { 67 | val params = it.parameters 68 | params.flashMode = Camera.Parameters.FLASH_MODE_OFF 69 | it.parameters = params 70 | it.stopPreview() 71 | } 72 | } 73 | 74 | override fun release() { 75 | super.release() 76 | 77 | invalidateCamera() 78 | } 79 | 80 | private fun initializeCamera() { 81 | camera = rearCamera() 82 | 83 | // Hack for some android versions 84 | try { 85 | camera?.setPreviewTexture(previewTexture) 86 | } catch (e: IOException) { 87 | Timber.e("Can't set preview texture") 88 | } 89 | } 90 | 91 | private fun invalidateCamera() { 92 | camera?.release() 93 | camera = null 94 | previewTexture = null 95 | } 96 | 97 | private fun isParameterSupported(value: String, supported: List?): Boolean { 98 | return supported != null && supported.indexOf(value) >= 0 99 | } 100 | 101 | private fun rearCamera(): Camera? { 102 | var cameraId = -1 103 | val info = CameraInfo() 104 | for (i in 0 until Camera.getNumberOfCameras() - 1) { 105 | Camera.getCameraInfo(i, info) 106 | if (info.facing == CameraInfo.CAMERA_FACING_BACK) { 107 | cameraId = i 108 | break 109 | } 110 | } 111 | if (cameraId < 0) { 112 | Timber.w("Wrong camera id $cameraId") 113 | return null 114 | } 115 | 116 | try { 117 | return Camera.open(cameraId) 118 | } catch (e: Exception) { 119 | Timber.e(e, "Exception when open camera $cameraId") 120 | } 121 | return null 122 | } 123 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modules/CameraFlashModuleV23.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modules 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import android.hardware.camera2.CameraAccessException 6 | import android.hardware.camera2.CameraCharacteristics 7 | import android.hardware.camera2.CameraManager 8 | import android.os.Build 9 | import androidx.annotation.RequiresApi 10 | import timber.log.Timber 11 | 12 | @RequiresApi(api = Build.VERSION_CODES.M) 13 | class CameraFlashModuleV23(context: Context) : BaseCameraFlashModule(context) { 14 | 15 | override val isAvailable get() = cameraManager != null && cameraId != null 16 | 17 | override val isSupported get() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) 18 | 19 | private var cameraManager: CameraManager? = null 20 | 21 | private var cameraId: String? = null 22 | 23 | init { 24 | cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager? 25 | 26 | cameraManager?.let { 27 | try { 28 | it.cameraIdList.forEach { id -> 29 | val characteristics = it.getCameraCharacteristics(id) 30 | val facing = characteristics.get(CameraCharacteristics.LENS_FACING) 31 | if (facing == CameraCharacteristics.LENS_FACING_BACK) { 32 | this.cameraId = id 33 | } 34 | } 35 | } catch (e: CameraAccessException) { 36 | Timber.e(e, "Can't get cameras list") 37 | } catch (e: IllegalArgumentException) { 38 | Timber.e(e, "Can't turn on flashlight") 39 | } 40 | } ?: run { 41 | Timber.e("Can't initialize CameraManager") 42 | } 43 | } 44 | 45 | override fun lightOn() { 46 | try { 47 | cameraId?.let { cameraManager?.setTorchMode(it, true) } 48 | } catch (e: CameraAccessException) { 49 | Timber.e(e, "Can't turn on flashlight") 50 | } catch (e: IllegalArgumentException) { 51 | Timber.e(e, "Can't turn on flashlight") 52 | } 53 | } 54 | 55 | override fun lightOff() { 56 | try { 57 | cameraId?.let { cameraManager?.setTorchMode(it, false) } 58 | } catch (e: CameraAccessException) { 59 | Timber.e(e, "Can't turn off flashlight") 60 | } catch (e: IllegalArgumentException) { 61 | Timber.e(e, "Can't turn on flashlight") 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modules/ModuleBase.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modules 2 | 3 | /** 4 | * Module implements light source like screen, camera flashlight 5 | */ 6 | interface ModuleBase { 7 | enum class Module { 8 | MODULE_SCREEN, MODULE_CAMERA_FLASHLIGHT 9 | } 10 | 11 | /** 12 | * Can we use this the module now or not. 13 | */ 14 | val isAvailable: Boolean 15 | 16 | /** 17 | * Hardware support the module or not. 18 | */ 19 | val isSupported: Boolean 20 | 21 | /** 22 | * Initialize and capture resources for module 23 | */ 24 | fun init() 25 | 26 | /** 27 | * Release resources for module 28 | */ 29 | fun release() 30 | 31 | /** 32 | * Set light brightnessO in percents 33 | */ 34 | fun setBrightness(percents: Int) 35 | 36 | /** 37 | * Check if module request runtime permissions and call permissions dialog if needed 38 | * Return true if permission do not required otherwise false 39 | */ 40 | fun checkPermissions(): Boolean 41 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modules/ModuleFactory.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modules 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | 6 | class ModuleFactory(private val context: Context) { 7 | 8 | fun getModule(module: ModuleBase.Module): ModuleBase { 9 | 10 | // Create new module 11 | if (module == ModuleBase.Module.MODULE_SCREEN) { 12 | return ScreenModule(context) 13 | } else if (module === ModuleBase.Module.MODULE_CAMERA_FLASHLIGHT) { 14 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 15 | CameraFlashModuleV23(context) 16 | } else { 17 | CameraFlashModuleV16(context) 18 | } 19 | } 20 | throw IllegalArgumentException(module.name + " module not implemented") 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/features/modules/ScreenModule.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.features.modules 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Build 6 | import android.provider.Settings 7 | import android.provider.Settings.SettingNotFoundException 8 | import androidx.localbroadcastmanager.content.LocalBroadcastManager 9 | import co.garmax.materialflashlight.ui.root.RootActivity 10 | import timber.log.Timber 11 | 12 | /** 13 | * Module for device screen 14 | */ 15 | class ScreenModule internal constructor(private val context: Context) : ModuleBase { 16 | 17 | override val isAvailable get() = true 18 | override val isSupported get() = true 19 | 20 | private var previousScreenBrightness = -1 21 | private var previousBrightnessMode = -1 22 | 23 | override fun init() { 24 | // Save initial values 25 | try { 26 | previousScreenBrightness = Settings.System.getInt( 27 | context.contentResolver, 28 | Settings.System.SCREEN_BRIGHTNESS 29 | ) 30 | previousBrightnessMode = Settings.System.getInt( 31 | context.contentResolver, 32 | Settings.System.SCREEN_BRIGHTNESS_MODE 33 | ) 34 | } catch (e: SettingNotFoundException) { 35 | Timber.e(e, "Can't read screen brightnessObservable settings") 36 | } 37 | 38 | // Set system values 39 | Settings.System.putInt(context.contentResolver, Settings.System.SCREEN_BRIGHTNESS, 255) 40 | Settings.System.putInt( 41 | context.contentResolver, 42 | Settings.System.SCREEN_BRIGHTNESS_MODE, 43 | Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL 44 | ) 45 | 46 | // Open activity with light screen 47 | context.startActivity( 48 | Intent( 49 | context, 50 | RootActivity::class.java 51 | ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 52 | ) 53 | } 54 | 55 | override fun release() { 56 | // Restore system values 57 | if (previousScreenBrightness >= 0) { 58 | Settings.System.putInt( 59 | context.contentResolver, 60 | Settings.System.SCREEN_BRIGHTNESS, previousScreenBrightness 61 | ) 62 | } 63 | if (previousBrightnessMode >= 0) { 64 | Settings.System.putInt( 65 | context.contentResolver, 66 | Settings.System.SCREEN_BRIGHTNESS_MODE, previousBrightnessMode 67 | ) 68 | } 69 | } 70 | 71 | override fun setBrightness(percents: Int) { 72 | // Pass value to the screen light 73 | val intent = Intent(ACTION_SCREEN_MODULE) 74 | intent.putExtra(EXTRA_BRIGHTNESS_PERCENT, percents) 75 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent) 76 | } 77 | 78 | override fun checkPermissions(): Boolean { 79 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.System.canWrite(context)) { 80 | Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).apply { 81 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 82 | }.let { 83 | context.startActivity(it) 84 | } 85 | 86 | return false 87 | } 88 | return true 89 | } 90 | 91 | companion object { 92 | const val ACTION_SCREEN_MODULE = "action_screen_module" 93 | const val EXTRA_BRIGHTNESS_PERCENT = "extra_brightness_percent" 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/repositories/SettingsRepository.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.repositories 2 | 3 | import android.content.Context 4 | import co.garmax.materialflashlight.features.modes.IntervalStrobeMode 5 | import co.garmax.materialflashlight.features.modes.ModeBase 6 | import co.garmax.materialflashlight.features.modules.ModuleBase 7 | 8 | class SettingsRepository(context: Context) { 9 | 10 | private val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) 11 | 12 | var isKeepScreenOn: Boolean 13 | get() = sharedPreferences.getBoolean(KEEP_SCREEN_ON, false) 14 | set(isKeepScreenOn) { 15 | sharedPreferences.edit().putBoolean(KEEP_SCREEN_ON, isKeepScreenOn).apply() 16 | } 17 | 18 | var isAutoTurnedOn: Boolean 19 | get() = sharedPreferences.getBoolean(AUTO_TURN_ON, false) 20 | set(isAutoTurnOn) { 21 | sharedPreferences.edit().putBoolean(AUTO_TURN_ON, isAutoTurnOn).apply() 22 | } 23 | 24 | var mode: ModeBase.Mode 25 | get() { 26 | val mode = sharedPreferences.getString(MODE, null) 27 | return mode?.let { ModeBase.Mode.valueOf(it) } ?: ModeBase.Mode.MODE_TORCH 28 | } 29 | set(mode) { 30 | sharedPreferences.edit().putString(MODE, mode.name).apply() 31 | } 32 | 33 | var module: ModuleBase.Module 34 | get() { 35 | val module = sharedPreferences.getString(MODULE, null) 36 | return module?.let { ModuleBase.Module.valueOf(it) } 37 | ?: ModuleBase.Module.MODULE_CAMERA_FLASHLIGHT 38 | } 39 | set(module) { 40 | sharedPreferences.edit().putString(MODULE, module.name).apply() 41 | } 42 | 43 | var strobeOnPeriod: Int 44 | get() { 45 | return sharedPreferences.getInt(STROBE_ON_PERIOD, IntervalStrobeMode.DEFAULT_STROBE_PERIOD) 46 | } 47 | set(value) { 48 | sharedPreferences.edit().putInt(STROBE_ON_PERIOD, value).apply() 49 | } 50 | 51 | var strobeOffPeriod: Int 52 | get() { 53 | return sharedPreferences.getInt(STROBE_OFF_PERIOD, IntervalStrobeMode.DEFAULT_DELAY_PERIOD) 54 | } 55 | set(value) { 56 | sharedPreferences.edit().putInt(STROBE_OFF_PERIOD, value).apply() 57 | } 58 | 59 | companion object { 60 | private const val KEEP_SCREEN_ON = "keep_screen_on" 61 | private const val MODE = "mode_name" 62 | private const val MODULE = "module_name" 63 | private const val AUTO_TURN_ON = "auto_turn_on" 64 | private const val STROBE_ON_PERIOD = "strobe_on_period" 65 | private const val STROBE_OFF_PERIOD = "strobe_off_period" 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/service/ForegroundService.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.service 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.app.PendingIntent 6 | import android.app.Service 7 | import android.content.Context 8 | import android.content.Intent 9 | import android.os.Build 10 | import android.os.IBinder 11 | import androidx.core.app.NotificationCompat 12 | import co.garmax.materialflashlight.R 13 | import co.garmax.materialflashlight.features.LightManager 14 | import org.koin.android.ext.android.inject 15 | 16 | /** 17 | * Service with notification 18 | */ 19 | class ForegroundService : Service() { 20 | 21 | private val lightManager: LightManager by inject() 22 | 23 | override fun onBind(intent: Intent): IBinder? { 24 | return null 25 | } 26 | 27 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 28 | intent ?: return super.onStartCommand(intent, flags, startId) 29 | 30 | val command = intent.getIntExtra(EXTRA_COMMAND, COMMAND_STOP) 31 | 32 | if (command == COMMAND_START) { 33 | startForeground() 34 | lightManager.turnOn() 35 | } else { 36 | lightManager.turnOff() 37 | stop() 38 | } 39 | 40 | return START_STICKY 41 | } 42 | 43 | private fun stop() { 44 | stopForeground(true) 45 | stopSelf() 46 | } 47 | 48 | // Start foreground service with notification 49 | private fun startForeground() { 50 | 51 | val intent = Intent(applicationContext, ForegroundService::class.java).apply { 52 | putExtra(EXTRA_COMMAND, COMMAND_STOP) 53 | } 54 | 55 | val pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_MUTABLE) 56 | 57 | val builder = NotificationCompat.Builder(this, CHANNEL_ID) 58 | .setSmallIcon(R.drawable.ic_light_notification) 59 | .setContentTitle(getString(R.string.notification_light)) 60 | .setPriority(NotificationCompat.PRIORITY_DEFAULT) 61 | .setOngoing(true) 62 | .setWhen(System.currentTimeMillis()) 63 | .addAction( 64 | R.drawable.ic_power_off, 65 | getString(R.string.notification_tap_to_turn_off), 66 | pendingIntent 67 | ) 68 | 69 | createNotificationChannel() 70 | 71 | startForeground(NOTIFICATION_ID, builder.build()) 72 | } 73 | 74 | private fun createNotificationChannel() { 75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 76 | val name = getString(R.string.channel_name) 77 | val descriptionText = getString(R.string.channel_description) 78 | val importance = NotificationManager.IMPORTANCE_DEFAULT 79 | val channel = NotificationChannel(CHANNEL_ID, name, importance).apply { 80 | description = descriptionText 81 | } 82 | // Register the channel with the system 83 | val notificationManager = 84 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 85 | notificationManager.createNotificationChannel(channel) 86 | } 87 | } 88 | 89 | companion object { 90 | private const val NOTIFICATION_ID = 1 91 | private const val EXTRA_COMMAND = "extra_command" 92 | private const val CHANNEL_ID = "main" 93 | 94 | private const val COMMAND_STOP = 0 95 | private const val COMMAND_START = 1 96 | 97 | fun startService(context: Context) { 98 | Intent(context, ForegroundService::class.java).apply { 99 | putExtra(EXTRA_COMMAND, COMMAND_START) 100 | }.let { 101 | context.startService(it) 102 | } 103 | } 104 | 105 | fun stopService(context: Context) { 106 | Intent(context, ForegroundService::class.java).apply { 107 | putExtra(EXTRA_COMMAND, COMMAND_STOP) 108 | }.let { 109 | context.startService(it) 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/service/LightTileService.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.service 2 | 3 | import android.graphics.drawable.Icon 4 | import android.os.Build 5 | import android.service.quicksettings.Tile 6 | import android.service.quicksettings.TileService 7 | import androidx.annotation.RequiresApi 8 | import co.garmax.materialflashlight.R 9 | import co.garmax.materialflashlight.features.LightManager 10 | import co.garmax.materialflashlight.utils.PostExecutionThread 11 | import io.reactivex.disposables.Disposable 12 | import org.koin.android.ext.android.inject 13 | 14 | /** 15 | * Service for android notification panel icon 16 | */ 17 | @RequiresApi(api = Build.VERSION_CODES.N) 18 | class LightTileService : TileService() { 19 | 20 | private val lightManager: LightManager by inject() 21 | 22 | private val postExecutionThread: PostExecutionThread by inject() 23 | 24 | private var disposableToggleState: Disposable? = null 25 | 26 | override fun onClick() { 27 | super.onClick() 28 | 29 | when (qsTile.state) { 30 | Tile.STATE_ACTIVE -> { 31 | setCurrentState(Tile.STATE_INACTIVE) 32 | lightManager.turnOff() 33 | } 34 | Tile.STATE_INACTIVE -> { 35 | setCurrentState(Tile.STATE_ACTIVE) 36 | lightManager.turnOn() 37 | } 38 | Tile.STATE_UNAVAILABLE -> { 39 | } 40 | } 41 | } 42 | 43 | private fun setCurrentState(state: Int) { 44 | val tile = qsTile 45 | qsTile.state = state 46 | 47 | when (state) { 48 | Tile.STATE_ACTIVE -> tile.icon = Icon.createWithResource( 49 | applicationContext, 50 | R.drawable.ic_quick_settings_active 51 | ) 52 | Tile.STATE_INACTIVE -> tile.icon = Icon.createWithResource( 53 | applicationContext, 54 | R.drawable.ic_quick_settings_inactive 55 | ) 56 | Tile.STATE_UNAVAILABLE -> tile.icon = Icon.createWithResource( 57 | applicationContext, 58 | R.drawable.ic_quick_settings_unavailable 59 | ) 60 | } 61 | tile.updateTile() 62 | } 63 | 64 | override fun onStartListening() { 65 | super.onStartListening() 66 | disposableToggleState = lightManager.toggleStateStream 67 | .observeOn(postExecutionThread.scheduler) 68 | .subscribe { isTurnedOn: Boolean -> setCurrentState(if (isTurnedOn) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE) } 69 | 70 | if (!lightManager.isSupported) setCurrentState(Tile.STATE_UNAVAILABLE) 71 | } 72 | 73 | override fun onStopListening() { 74 | super.onStopListening() 75 | 76 | disposableToggleState?.dispose() 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui 2 | 3 | import android.os.Build 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | 7 | abstract class BaseFragment : Fragment() { 8 | 9 | open val isInImmersiveMode = false 10 | 11 | override fun onResume() { 12 | super.onResume() 13 | 14 | if (isInImmersiveMode) setFullscreen() else exitFullscreen() 15 | } 16 | 17 | private fun setFullscreen() { 18 | var flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN 19 | 20 | if (Build.VERSION.SDK_INT >= 19) { 21 | flags = 22 | flags or (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or 23 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) 24 | } 25 | 26 | requireActivity().window.decorView.systemUiVisibility = flags 27 | } 28 | 29 | private fun exitFullscreen() { 30 | requireActivity().window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/PermissionsActivity.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.os.Bundle 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.core.app.ActivityCompat 9 | import co.garmax.materialflashlight.features.LightManager 10 | import org.koin.android.ext.android.inject 11 | 12 | /** 13 | * Activity to call runtime permission from any place like service, widget or activity 14 | */ 15 | class PermissionsActivity : AppCompatActivity() { 16 | 17 | private val lightManager: LightManager by inject() 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | 22 | intent.getStringArrayExtra(EXTRA_PERMISSIONS_ARRAY)?.let { 23 | ActivityCompat.requestPermissions(this, it, RC_CHECK_PERMISSION) 24 | } 25 | } 26 | 27 | override fun onRequestPermissionsResult( 28 | requestCode: Int, 29 | permissions: Array, 30 | grantResults: IntArray 31 | ) { 32 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 33 | 34 | if (requestCode == RC_CHECK_PERMISSION) { 35 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 36 | lightManager.turnOn() 37 | } 38 | } 39 | 40 | finish() 41 | } 42 | 43 | companion object { 44 | private const val RC_CHECK_PERMISSION = 0 45 | 46 | private const val EXTRA_PERMISSIONS_ARRAY = "extra_permissions_array" 47 | 48 | fun startActivity(context: Context, permissions: Array) { 49 | Intent(context, PermissionsActivity::class.java).apply { 50 | putExtra(EXTRA_PERMISSIONS_ARRAY, permissions) 51 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 52 | }.let { 53 | context.startActivity(it) 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/light/LightFragment.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui.light 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.graphics.Color 8 | import android.os.Bundle 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import androidx.localbroadcastmanager.content.LocalBroadcastManager 13 | import co.garmax.materialflashlight.databinding.FragmentLightBinding 14 | import co.garmax.materialflashlight.features.LightManager 15 | import co.garmax.materialflashlight.features.modules.ScreenModule 16 | import co.garmax.materialflashlight.repositories.SettingsRepository 17 | import co.garmax.materialflashlight.ui.BaseFragment 18 | import org.koin.android.ext.android.inject 19 | 20 | /** 21 | * Light simulation screen 22 | */ 23 | class LightFragment : BaseFragment() { 24 | 25 | override val isInImmersiveMode = true 26 | 27 | private val lightManager: LightManager by inject() 28 | 29 | private val settingsRepository: SettingsRepository by inject() 30 | 31 | private val binding get() = _binding!! 32 | private var _binding: FragmentLightBinding? = null 33 | 34 | /** 35 | * Receive and handle commands from screen module 36 | */ 37 | private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { 38 | override fun onReceive(context: Context, intent: Intent) { 39 | // Get brightnessObservable value 40 | val brightness: Int = intent.getIntExtra(ScreenModule.EXTRA_BRIGHTNESS_PERCENT, 100) 41 | setBrightness(brightness) 42 | } 43 | } 44 | 45 | override fun onCreateView( 46 | inflater: LayoutInflater, 47 | container: ViewGroup?, 48 | savedInstanceState: Bundle? 49 | ): View { 50 | _binding = FragmentLightBinding.inflate(inflater, container, false) 51 | return binding.root 52 | } 53 | 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 55 | super.onViewCreated(view, savedInstanceState) 56 | 57 | binding.fab.keepScreenOn = settingsRepository.isKeepScreenOn 58 | binding.fab.setOnClickListener { lightManager.turnOff() } 59 | } 60 | 61 | override fun onResume() { 62 | super.onResume() 63 | LocalBroadcastManager.getInstance( 64 | requireContext() 65 | ).registerReceiver( 66 | broadcastReceiver, IntentFilter(ScreenModule.ACTION_SCREEN_MODULE) 67 | ) 68 | } 69 | 70 | override fun onPause() { 71 | super.onPause() 72 | 73 | LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(broadcastReceiver) 74 | } 75 | 76 | override fun onStop() { 77 | super.onStop() 78 | 79 | // Turn off light because screen not visible and for this mode it makes no sense 80 | if (lightManager.isTurnedOn) lightManager.turnOff() 81 | } 82 | 83 | private fun setBrightness(percent: Int) { 84 | val color = 255 * percent / 100 85 | binding.layoutRoot.setBackgroundColor(Color.rgb(color, color, color)) 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/main/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui.main 2 | 3 | import android.animation.ArgbEvaluator 4 | import android.animation.ValueAnimator 5 | import android.os.Bundle 6 | import android.text.Editable 7 | import android.text.TextWatcher 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import androidx.annotation.ColorRes 12 | import androidx.core.content.ContextCompat 13 | import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat 14 | import co.garmax.materialflashlight.BuildConfig 15 | import co.garmax.materialflashlight.R 16 | import co.garmax.materialflashlight.databinding.FragmentMainBinding 17 | import co.garmax.materialflashlight.extensions.observeNotNull 18 | import co.garmax.materialflashlight.features.modes.ModeBase.Mode 19 | import co.garmax.materialflashlight.features.modules.ModuleBase.Module 20 | import co.garmax.materialflashlight.service.ForegroundService 21 | import co.garmax.materialflashlight.ui.BaseFragment 22 | import org.koin.androidx.viewmodel.ext.android.viewModel 23 | import timber.log.Timber 24 | 25 | class MainFragment : BaseFragment() { 26 | 27 | private val viewModel by viewModel() 28 | 29 | private val binding get() = _binding!! 30 | private var _binding: FragmentMainBinding? = null 31 | 32 | private var animatedDrawableDay: AnimatedVectorDrawableCompat? = null 33 | private var animatedDrawableNight: AnimatedVectorDrawableCompat? = null 34 | private var backgroundColorAnimation: ValueAnimator? = null 35 | 36 | override fun onCreateView( 37 | inflater: LayoutInflater, 38 | container: ViewGroup?, 39 | savedInstanceState: Bundle? 40 | ): View { 41 | _binding = FragmentMainBinding.inflate(inflater, container, false) 42 | return binding.root 43 | } 44 | 45 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 46 | super.onViewCreated(view, savedInstanceState) 47 | 48 | setupLayout(savedInstanceState) 49 | setupViewModel() 50 | } 51 | 52 | private fun setupLayout(savedInstanceState: Bundle?) { 53 | 54 | animatedDrawableDay = 55 | AnimatedVectorDrawableCompat.create(requireContext(), R.drawable.avc_appbar_day) 56 | animatedDrawableNight = 57 | AnimatedVectorDrawableCompat.create(requireContext(), R.drawable.avc_appbar_night) 58 | 59 | with(binding) { 60 | 61 | fab.setOnClickListener { 62 | if (viewModel.isLightTurnedOn) { 63 | ForegroundService.stopService(requireContext()) 64 | } else { 65 | ForegroundService.startService(requireContext()) 66 | } 67 | } 68 | 69 | layoutKeepScreenOn.setOnClickListener { binding.switchKeepScreenOn.toggle() } 70 | 71 | layoutAutoTurnOn.setOnClickListener { binding.switchAutoTurnOn.toggle() } 72 | 73 | layoutContent.setBackgroundColor( 74 | ContextCompat.getColor( 75 | requireContext(), 76 | if (viewModel.isLightTurnedOn) R.color.green else R.color.colorPrimaryLight 77 | ) 78 | ) 79 | 80 | if (savedInstanceState == null) { 81 | // Set module 82 | when (viewModel.lightModule) { 83 | Module.MODULE_CAMERA_FLASHLIGHT -> radioCameraFlashlight.isChecked = true 84 | Module.MODULE_SCREEN -> radioScreen.isChecked = true 85 | } 86 | when (viewModel.lightMode) { 87 | Mode.MODE_INTERVAL_STROBE -> radioIntervalStrobe.isChecked = true 88 | Mode.MODE_TORCH -> radioTorch.isChecked = true 89 | Mode.MODE_SOUND_STROBE -> radioSoundStrobe.isChecked = true 90 | Mode.MODE_SOS -> radioSos.isChecked = true 91 | } 92 | intervalStrobeOn.setText(viewModel.strobeOnPeriod.toString()) 93 | intervalStrobeOff.setText(viewModel.strobeOffPeriod.toString()) 94 | } else { 95 | setState(viewModel.isLightTurnedOn, false) 96 | } 97 | 98 | intervalStrobeTiming.visibility = 99 | if (radioIntervalStrobe.isChecked) View.VISIBLE 100 | else View.GONE 101 | 102 | switchKeepScreenOn.isChecked = viewModel.isKeepScreenOn 103 | fab.keepScreenOn = viewModel.isKeepScreenOn 104 | 105 | switchAutoTurnOn.isChecked = viewModel.isAutoTurnedOn 106 | 107 | radioSoundStrobe.setOnCheckedChangeListener { _, isChecked -> 108 | if (isChecked) viewModel.setMode(Mode.MODE_SOUND_STROBE) 109 | } 110 | radioIntervalStrobe.setOnCheckedChangeListener { _, isChecked -> 111 | if (isChecked) { 112 | viewModel.setMode(Mode.MODE_INTERVAL_STROBE) 113 | intervalStrobeTiming.visibility = View.VISIBLE 114 | } else { 115 | intervalStrobeTiming.visibility = View.GONE 116 | } 117 | } 118 | radioTorch.setOnCheckedChangeListener { _, isChecked -> 119 | if (isChecked) viewModel.setMode(Mode.MODE_TORCH) 120 | } 121 | radioSos.setOnCheckedChangeListener { _, isChecked -> 122 | if (isChecked) viewModel.setMode(Mode.MODE_SOS) 123 | } 124 | radioCameraFlashlight.setOnCheckedChangeListener { _, isChecked -> 125 | if (isChecked) viewModel.setModule(Module.MODULE_CAMERA_FLASHLIGHT) 126 | } 127 | radioScreen.setOnCheckedChangeListener { _, isChecked -> 128 | if (isChecked) viewModel.setModule(Module.MODULE_SCREEN) 129 | } 130 | 131 | switchKeepScreenOn.setOnCheckedChangeListener { _, isChecked -> 132 | viewModel.isKeepScreenOn = isChecked 133 | fab.keepScreenOn = isChecked 134 | } 135 | 136 | switchAutoTurnOn.setOnCheckedChangeListener { _, isChecked -> 137 | viewModel.isAutoTurnedOn = isChecked 138 | } 139 | 140 | val textWatcherObject = object: TextWatcher { 141 | override fun afterTextChanged(p0: Editable?) {} 142 | 143 | override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} 144 | 145 | override fun onTextChanged(p0: CharSequence?, start: Int, before: Int, count: Int) { 146 | val strobeOnString = intervalStrobeOn.text.toString() 147 | val strobeOn = if (strobeOnString == "") 0 else Integer.parseInt(strobeOnString) 148 | 149 | val strobeOffString = intervalStrobeOff.text.toString() 150 | val strobeOff = if (strobeOffString == "") 0 else Integer.parseInt(strobeOffString) 151 | 152 | viewModel.setStrobePeriod(strobeOn, strobeOff) 153 | } 154 | } 155 | 156 | intervalStrobeOn.addTextChangedListener(textWatcherObject) 157 | intervalStrobeOff.addTextChangedListener(textWatcherObject) 158 | 159 | textVersion.text = getString(R.string.text_version, BuildConfig.VERSION_NAME) 160 | } 161 | } 162 | 163 | private fun setupViewModel() { 164 | // Handle toggle of the light 165 | observeNotNull(viewModel.liveDataLightToggle) { setState(it, true) } 166 | } 167 | 168 | private fun setState(isLightOn: Boolean, animated: Boolean) { 169 | Timber.d("Light toggle %s, animated %s", isLightOn, animated) 170 | 171 | with(binding) { 172 | if (isLightOn) { 173 | // Fab image 174 | fab.setImageResource(R.drawable.ic_power_on) 175 | 176 | // Appbar image 177 | if (animated) { 178 | imageAppbar.setImageDrawable(animatedDrawableDay) 179 | animatedDrawableDay?.start() 180 | animateBackground(R.color.colorPrimaryLight, R.color.green) 181 | } else { 182 | imageAppbar.setImageResource(R.drawable.vc_appbar_day) 183 | layoutContent.setBackgroundResource(R.color.green) 184 | } 185 | } else { 186 | // Fab image 187 | fab.setImageResource(R.drawable.ic_power_off) 188 | 189 | // Appbar image 190 | if (animated) { 191 | imageAppbar.setImageDrawable(animatedDrawableNight) 192 | animatedDrawableNight?.start() 193 | animateBackground(R.color.green, R.color.colorPrimaryLight) 194 | } else { 195 | imageAppbar.setImageResource(R.drawable.vc_appbar_night) 196 | layoutContent.setBackgroundResource(R.color.colorPrimaryLight) 197 | } 198 | } 199 | } 200 | } 201 | 202 | private fun animateBackground(@ColorRes fromColorResId: Int, @ColorRes toColorResId: Int) { 203 | val colorFrom: Int = ContextCompat.getColor(requireContext(), fromColorResId) 204 | val colorTo: Int = ContextCompat.getColor(requireContext(), toColorResId) 205 | 206 | if (backgroundColorAnimation?.isRunning == true) { 207 | backgroundColorAnimation?.cancel() 208 | } 209 | 210 | backgroundColorAnimation = 211 | ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo).apply { 212 | duration = resources.getInteger(R.integer.animation_time).toLong() 213 | addUpdateListener { animator: ValueAnimator -> 214 | binding.layoutContent.setBackgroundColor( 215 | animator.animatedValue as Int 216 | ) 217 | } 218 | start() 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui.main 2 | 3 | import androidx.lifecycle.ViewModel 4 | import co.garmax.materialflashlight.extensions.liveDataOf 5 | import co.garmax.materialflashlight.features.LightManager 6 | import co.garmax.materialflashlight.features.modes.ModeBase 7 | import co.garmax.materialflashlight.features.modes.ModeFactory 8 | import co.garmax.materialflashlight.features.modules.ModuleBase 9 | import co.garmax.materialflashlight.features.modules.ModuleFactory 10 | import co.garmax.materialflashlight.repositories.SettingsRepository 11 | import co.garmax.materialflashlight.utils.PostExecutionThread 12 | import io.reactivex.disposables.Disposable 13 | 14 | class MainViewModel( 15 | postExecutionThread: PostExecutionThread, 16 | private val lightManager: LightManager, 17 | private val settingsRepository: SettingsRepository, 18 | private val modeFactory: ModeFactory, 19 | private val moduleFactory: ModuleFactory 20 | ) : ViewModel() { 21 | 22 | val liveDataLightToggle = liveDataOf() 23 | 24 | var isAutoTurnedOn: Boolean 25 | get() = settingsRepository.isAutoTurnedOn 26 | set(value) { 27 | settingsRepository.isAutoTurnedOn = value 28 | } 29 | 30 | var isKeepScreenOn: Boolean 31 | get() = settingsRepository.isKeepScreenOn 32 | set(value) { 33 | settingsRepository.isKeepScreenOn = value 34 | } 35 | 36 | val isLightTurnedOn get() = lightManager.isTurnedOn 37 | 38 | val lightMode get() = settingsRepository.mode 39 | 40 | val lightModule get() = settingsRepository.module 41 | 42 | val strobeOnPeriod get() = settingsRepository.strobeOnPeriod 43 | val strobeOffPeriod get() = settingsRepository.strobeOffPeriod 44 | 45 | private var disposableLightToggle: Disposable? = null 46 | 47 | init { 48 | disposableLightToggle = lightManager 49 | .toggleStateStream 50 | .observeOn(postExecutionThread.scheduler) 51 | .subscribe { liveDataLightToggle.value = it } 52 | } 53 | 54 | fun setMode(mode: ModeBase.Mode) { 55 | settingsRepository.mode = mode 56 | lightManager.setMode(modeFactory.getMode(mode)) 57 | } 58 | 59 | fun setModule(module: ModuleBase.Module) { 60 | settingsRepository.module = module 61 | lightManager.setModule(moduleFactory.getModule(module)) 62 | } 63 | 64 | fun setStrobePeriod(timeOn: Int, timeOff: Int) { 65 | settingsRepository.strobeOnPeriod = timeOn 66 | settingsRepository.strobeOffPeriod = timeOff 67 | lightManager.setStrobePeriod(timeOn, timeOff) 68 | } 69 | 70 | override fun onCleared() { 71 | super.onCleared() 72 | 73 | disposableLightToggle?.dispose() 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/root/RootActivity.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui.root 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import co.garmax.materialflashlight.R 6 | import co.garmax.materialflashlight.extensions.observeNotNull 7 | import co.garmax.materialflashlight.features.modules.ModuleBase 8 | import co.garmax.materialflashlight.ui.light.LightFragment 9 | import co.garmax.materialflashlight.ui.main.MainFragment 10 | import org.koin.androidx.viewmodel.ext.android.viewModel 11 | 12 | class RootActivity : AppCompatActivity() { 13 | 14 | private val viewModel by viewModel() 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_root) 19 | 20 | if (savedInstanceState == null) { 21 | replaceFragment(viewModel.isAutoTurnOn) 22 | 23 | // Handle auto turn on 24 | if (viewModel.isAutoTurnOn) viewModel.toggleLight(true) 25 | } 26 | 27 | setupViewModel() 28 | } 29 | 30 | private fun setupViewModel() { 31 | observeNotNull(viewModel.liveDataLightToggle) { replaceFragment(it) } 32 | } 33 | 34 | private fun replaceFragment(isTunedOn: Boolean) { 35 | // If module is screen and turned on 36 | val fragment = if (isTunedOn && viewModel.lightModule == ModuleBase.Module.MODULE_SCREEN) { 37 | LightFragment() 38 | } else { 39 | MainFragment() 40 | } 41 | 42 | val fragmentCurrent = supportFragmentManager.findFragmentById(R.id.layout_container) 43 | 44 | // Change fragment only if fragment is different 45 | if (fragmentCurrent == null || fragment.javaClass != fragmentCurrent.javaClass) { 46 | getSupportFragmentManager() 47 | .beginTransaction() 48 | .replace(R.id.layout_container, fragment, fragment.javaClass.name) 49 | .commit() 50 | } 51 | } 52 | 53 | override fun onBackPressed() { 54 | super.onBackPressed() 55 | 56 | // Stop service if user close app 57 | viewModel.toggleLight(false) 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/root/RootViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui.root 2 | 3 | import androidx.lifecycle.ViewModel 4 | import co.garmax.materialflashlight.extensions.liveDataOf 5 | import co.garmax.materialflashlight.features.LightManager 6 | import co.garmax.materialflashlight.repositories.SettingsRepository 7 | import co.garmax.materialflashlight.utils.PostExecutionThread 8 | import io.reactivex.disposables.Disposable 9 | 10 | class RootViewModel( 11 | postExecutionThread: PostExecutionThread, 12 | private val lightManager: LightManager, 13 | private val settingsRepository: SettingsRepository 14 | ) : ViewModel() { 15 | 16 | val liveDataLightToggle = liveDataOf() 17 | 18 | val isAutoTurnOn get() = settingsRepository.isAutoTurnedOn 19 | 20 | val lightModule get() = settingsRepository.module 21 | 22 | private var disposableLightToggle: Disposable? = null 23 | 24 | init { 25 | disposableLightToggle = lightManager 26 | .toggleStateStream 27 | .observeOn(postExecutionThread.scheduler) 28 | .subscribe { liveDataLightToggle.value = it } 29 | } 30 | 31 | fun toggleLight(isTurnOn: Boolean) { 32 | if (isTurnOn) lightManager.turnOn() else lightManager.turnOff() 33 | } 34 | 35 | override fun onCleared() { 36 | super.onCleared() 37 | 38 | disposableLightToggle?.dispose() 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/ui/views/MaskedScrollView.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.ui.views 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import androidx.core.widget.NestedScrollView 7 | import co.garmax.materialflashlight.extensions.asDp 8 | 9 | /** 10 | * Add transparent mask at top and bottom of the view 11 | */ 12 | class MaskedScrollView @JvmOverloads constructor( 13 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 14 | ) : NestedScrollView(context, attrs, defStyleAttr) { 15 | 16 | private val gradientSize = 16.asDp.toFloat() 17 | 18 | private val topPaint = Paint().apply { 19 | xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) 20 | shader = LinearGradient(0f, 0f, 0f, gradientSize, 0, Color.WHITE, Shader.TileMode.CLAMP) 21 | } 22 | 23 | private var bottomPaint: Paint? = null 24 | 25 | private var translationY = 0 26 | 27 | init { 28 | setLayerType(LAYER_TYPE_HARDWARE, null) 29 | setWillNotDraw(false) 30 | 31 | setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, _: Int -> 32 | translationY = scrollY 33 | } 34 | } 35 | 36 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 37 | super.onSizeChanged(w, h, oldw, oldh) 38 | createBottomPaint() 39 | } 40 | 41 | override fun dispatchDraw(canvas: Canvas) { 42 | super.dispatchDraw(canvas) 43 | 44 | with(canvas) { 45 | if (bottomPaint == null && height > 0) { 46 | createBottomPaint() 47 | } 48 | 49 | save() 50 | translate(0f, translationY.toFloat()) 51 | 52 | // Bottom mask 53 | bottomPaint?.let { 54 | drawRect( 55 | 0f, ( 56 | height - gradientSize), 57 | width.toFloat(), 58 | height.toFloat(), 59 | it 60 | ) 61 | } 62 | 63 | // Top mask 64 | drawRect(0f, 0f, width.toFloat(), gradientSize, topPaint) 65 | restore() 66 | } 67 | } 68 | 69 | private fun createBottomPaint() { 70 | bottomPaint = Paint().apply { 71 | xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) 72 | shader = LinearGradient( 73 | 0f, 74 | (height - gradientSize), 75 | 0f, 76 | height.toFloat(), 77 | Color.WHITE, 78 | 0, 79 | Shader.TileMode.CLAMP 80 | ) 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/utils/PostExecutionThread.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.utils 2 | 3 | import io.reactivex.Scheduler 4 | 5 | interface PostExecutionThread { 6 | val scheduler: Scheduler 7 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/widget/WidgetManager.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.widget 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.ComponentName 5 | import android.content.Context 6 | import android.content.Intent 7 | import co.garmax.materialflashlight.widget.WidgetProviderButton 8 | 9 | class WidgetManager(private val context: Context) { 10 | 11 | fun updateWidgets() { 12 | // Update widgets 13 | val idsWidgetButton = AppWidgetManager.getInstance(context) 14 | .getAppWidgetIds(ComponentName(context, WidgetProviderButton::class.java)) 15 | val intentButton = Intent(context, WidgetProviderButton::class.java) 16 | .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE) 17 | .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idsWidgetButton) 18 | context.sendBroadcast(intentButton) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/co/garmax/materialflashlight/widget/WidgetProviderButton.kt: -------------------------------------------------------------------------------- 1 | package co.garmax.materialflashlight.widget 2 | 3 | import android.app.PendingIntent 4 | import android.appwidget.AppWidgetManager 5 | import android.appwidget.AppWidgetProvider 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.graphics.Bitmap 9 | import android.graphics.Canvas 10 | import android.os.Build 11 | import android.widget.RemoteViews 12 | import androidx.core.content.ContextCompat 13 | import androidx.core.graphics.drawable.DrawableCompat 14 | import co.garmax.materialflashlight.R 15 | import co.garmax.materialflashlight.features.LightManager 16 | import org.koin.core.component.KoinComponent 17 | import org.koin.core.component.inject 18 | 19 | class WidgetProviderButton : AppWidgetProvider(), KoinComponent { 20 | 21 | private val lightManager: LightManager by inject() 22 | 23 | private val widgetManager: WidgetManager by inject() 24 | 25 | override fun onUpdate( 26 | context: Context, 27 | appWidgetManager: AppWidgetManager, 28 | appWidgetIds: IntArray 29 | ) { 30 | super.onUpdate(context, appWidgetManager, appWidgetIds) 31 | 32 | // Perform this loop procedure for each App Widget that belongs to this provider 33 | for (appWidgetId in appWidgetIds) { 34 | val views = RemoteViews(context.packageName, R.layout.view_widget_button) 35 | 36 | // Set on intent to handle onclick 37 | views.setOnClickPendingIntent(R.id.button_widget, getPendingSelfIntent(context)) 38 | 39 | // Set image according to current state 40 | if (lightManager.isTurnedOn) { 41 | setWidgetImage(context, views, R.id.button_widget, R.drawable.ic_widget_button_on) 42 | } else { 43 | setWidgetImage(context, views, R.id.button_widget, R.drawable.ic_widget_button_off) 44 | } 45 | 46 | // Tell the AppWidgetManager to perform an update on the current app widget 47 | appWidgetManager.updateAppWidget(appWidgetId, views) 48 | } 49 | } 50 | 51 | override fun onReceive(context: Context, intent: Intent) { 52 | super.onReceive(context, intent) 53 | if (ACTION_WIDGET_BUTTON_CLICK == intent.getAction()) { 54 | if (lightManager.isTurnedOn) lightManager.turnOff() else lightManager.turnOn() 55 | 56 | widgetManager.updateWidgets() 57 | } 58 | } 59 | 60 | private fun getPendingSelfIntent(context: Context): PendingIntent { 61 | // An explicit intent directed at the current class (the "self"). 62 | val intent = Intent(context, javaClass).apply { 63 | action = ACTION_WIDGET_BUTTON_CLICK 64 | } 65 | 66 | return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE) 67 | } 68 | 69 | private fun setWidgetImage( 70 | context: Context, 71 | remoteViews: RemoteViews, 72 | viewRes: Int, 73 | imageRes: Int 74 | ) { 75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 76 | remoteViews.setImageViewResource(viewRes, imageRes) 77 | } else { 78 | ContextCompat.getDrawable(context, imageRes)?.let { 79 | val drawable = DrawableCompat.wrap(it).mutate() 80 | val bitmap = Bitmap.createBitmap( 81 | drawable.intrinsicWidth, 82 | drawable.intrinsicHeight, 83 | Bitmap.Config.ARGB_8888 84 | ) 85 | 86 | val canvas = Canvas(bitmap) 87 | drawable.setBounds(0, 0, canvas.width, canvas.height) 88 | drawable.draw(canvas) 89 | remoteViews.setImageViewBitmap(viewRes, bitmap) 90 | } 91 | } 92 | } 93 | 94 | companion object { 95 | const val ACTION_WIDGET_BUTTON_CLICK = 96 | "co.garmax.materialflashlight.action.WIDGET_BUTTON_CLICK" 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_background_day.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_background_night.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_clouds_day.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_clouds_night.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_grass_day.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_grass_night.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_hide.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_show.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_stars_day.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/animator/appbar_stars_night.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_light_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/drawable-hdpi/ic_light_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_light_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/drawable-mdpi/ic_light_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/ic_widget_button_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/drawable-nodpi/ic_widget_button_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_light_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/drawable-xhdpi/ic_light_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_light_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/drawable-xxhdpi/ic_light_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_light_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/drawable-xxxhdpi/ic_light_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avc_appbar_day.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 13 | 14 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 41 | 42 | 45 | 46 | 49 | 50 | 53 | 54 | 57 | 58 | 61 | 62 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/avc_appbar_night.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 38 | 39 | 42 | 43 | 46 | 47 | 50 | 51 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_status_bar_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_power_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_power_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_quick_settings_active.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_quick_settings_inactive.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_quick_settings_unavailable.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_widget_button_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_widget_button_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vc_appbar_night.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 71 | 74 | 77 | 78 | 82 | 86 | 90 | 94 | 98 | 101 | 104 | 107 | 110 | 113 | 116 | 119 | 122 | 125 | 128 | 131 | 134 | 137 | 140 | 143 | 146 | 149 | 152 | 155 | 158 | 161 | 164 | 167 | 170 | 173 | 176 | 179 | 182 | 185 | 188 | 191 | 194 | 197 | 200 | 203 | 206 | 210 | 213 | 216 | 219 | 222 | 225 | 228 | 231 | 234 | 237 | 240 | 243 | 246 | 249 | 252 | 255 | 258 | 261 | 264 | 267 | 270 | 273 | 276 | 279 | 282 | 285 | 288 | 291 | 294 | 297 | 300 | 303 | 306 | 309 | 312 | 315 | 318 | 321 | 324 | 327 | 330 | 333 | 336 | 339 | 342 | 345 | 348 | 351 | 354 | 357 | 360 | 363 | 366 | 369 | 372 | 373 | 374 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_root.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 27 | 28 | 34 | 35 | 42 | 43 | 51 | 52 | 58 | 59 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 88 | 89 | 90 | 91 | 99 | 100 | 105 | 106 | 112 | 113 | 114 | 115 | 122 | 123 | 128 | 129 | 135 | 136 | 141 | 142 | 147 | 148 | 149 | 150 | 159 | 160 | 164 | 165 | 169 | 170 | 178 | 179 | 180 | 184 | 185 | 189 | 190 | 198 | 199 | 200 | 201 | 202 | 208 | 209 | 210 | 211 | 212 | 213 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_widget_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-hdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-mdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Material Фонарик 3 | 4 | Держать экран включенным 5 | Гореть 6 | Мигать с интервалом 7 | Мигать пo звукам 8 | SOS 9 | Включить при запуске 10 | Версия: %s 11 | Экран 12 | Вспышка камеры 13 | 14 | Выбранный модуль не поддерживается на данном устройстве, попробуйте другой. 15 | Выбранный модуль не может быть использован сейчас, попробуйте другой. 16 | 17 | Cвет 18 | 19 | Свет 20 | Свет 21 | 22 | Свет включён 23 | Нажмите, чтобы отключить 24 | 25 | Другое приложение использует вспышку камеры 26 | 27 | Выключен (мс) 28 | Включен (мс) 29 | -------------------------------------------------------------------------------- /app/src/main/res/values-v19/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64dp 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #9C27B0 4 | #7B1FA2 5 | #E1BEE7 6 | #448AFF 7 | #212121 8 | #DD666666 9 | #00666666 10 | 11 | 12 | #99FFFFFF 13 | #4CAF50 14 | #FFFFFF 15 | #000000 16 | #B3FFFFFF 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 240dp 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/integer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1000 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Material Flashlight 3 | 4 | Keep Screen On 5 | Torch 6 | Interval Strobe 7 | Music Strobe 8 | SOS 9 | Auto turn on flash on start 10 | Version: %s 11 | Screen 12 | Camera Flashlight 13 | 14 | Selected module is not supported on this device, try another. 15 | Selected module can\'t be used now, try another. 16 | 17 | Light 18 | 19 | Light 20 | Light 21 | 22 | The light is on 23 | Tap to turn off 24 | 25 | Another application is using camera\'s flash 26 | 27 | Time Off (ms) 28 | Time On (ms) 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/xml/widget_info_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath "com.android.tools.build:gradle:7.0.4" 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0" 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garmax1/material-flashlight/4f18a74351043d47f742f869c03322ab189cdbc3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 04 21:32:37 MSK 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------