Switch from Dagger to Koin, resolves #35

This commit is contained in:
Aidan Follestad 2018-12-06 17:56:51 -08:00
commit 1e92644904
61 changed files with 385 additions and 660 deletions

2
.idea/modules.xml generated
View file

@ -3,11 +3,11 @@
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/common/common.iml" filepath="$PROJECT_DIR$/common/common.iml" />
<module fileurl="file://$PROJECT_DIR$/data/data.iml" filepath="$PROJECT_DIR$/data/data.iml" /> <module fileurl="file://$PROJECT_DIR$/data/data.iml" filepath="$PROJECT_DIR$/data/data.iml" />
<module fileurl="file://$PROJECT_DIR$/engine/engine.iml" filepath="$PROJECT_DIR$/engine/engine.iml" /> <module fileurl="file://$PROJECT_DIR$/engine/engine.iml" filepath="$PROJECT_DIR$/engine/engine.iml" />
<module fileurl="file://$PROJECT_DIR$/nock-nock.iml" filepath="$PROJECT_DIR$/nock-nock.iml" /> <module fileurl="file://$PROJECT_DIR$/nock-nock.iml" filepath="$PROJECT_DIR$/nock-nock.iml" />
<module fileurl="file://$PROJECT_DIR$/notifications/notifications.iml" filepath="$PROJECT_DIR$/notifications/notifications.iml" /> <module fileurl="file://$PROJECT_DIR$/notifications/notifications.iml" filepath="$PROJECT_DIR$/notifications/notifications.iml" />
<module fileurl="file://$PROJECT_DIR$/utilities/utilities.iml" filepath="$PROJECT_DIR$/utilities/utilities.iml" />
<module fileurl="file://$PROJECT_DIR$/viewcomponents/viewcomponents.iml" filepath="$PROJECT_DIR$/viewcomponents/viewcomponents.iml" /> <module fileurl="file://$PROJECT_DIR$/viewcomponents/viewcomponents.iml" filepath="$PROJECT_DIR$/viewcomponents/viewcomponents.iml" />
</modules> </modules>
</component> </component>

View file

@ -18,7 +18,7 @@ android {
} }
dependencies { dependencies {
implementation project(':utilities') implementation project(':common')
implementation project(':engine') implementation project(':engine')
implementation project(':data') implementation project(':data')
implementation project(':notifications') implementation project(':notifications')
@ -32,16 +32,22 @@ dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin
implementation 'com.google.dagger:dagger:' + versions.dagger implementation 'org.koin:koin-android:' + versions.koin
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger implementation 'org.koin:koin-androidx-scope:' + versions.koin
implementation 'org.koin:koin-androidx-viewmodel:' + versions.koin
implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
implementation 'com.jakewharton.timber:timber:' + versions.timber implementation 'com.jakewharton.timber:timber:' + versions.timber
testImplementation 'junit:junit:' + versions.junit testImplementation 'junit:junit:' + versions.junit
testImplementation 'org.mockito:mockito-core:' + versions.mockito testImplementation 'org.mockito:mockito-core:' + versions.mockito
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin
testImplementation 'com.google.truth:truth:' + versions.truth testImplementation 'com.google.truth:truth:' + versions.truth
testImplementation 'androidx.arch.core:core-testing:' + versions.archTesting
androidTestImplementation 'androidx.test:runner:' + versions.androidxTestRunner
androidTestImplementation 'androidx.test:rules:' + versions.androidxTestRunner
} }
apply from: '../spotless.gradle' apply from: '../spotless.gradle'

View file

@ -16,55 +16,42 @@
package com.afollestad.nocknock package com.afollestad.nocknock
import android.app.Application import android.app.Application
import com.afollestad.nocknock.di.AppComponent import com.afollestad.nocknock.engine.engineModule
import com.afollestad.nocknock.di.DaggerAppComponent import com.afollestad.nocknock.koin.mainModule
import com.afollestad.nocknock.engine.validation.BootReceiver import com.afollestad.nocknock.koin.viewModelModule
import com.afollestad.nocknock.engine.validation.ValidationJob
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.ui.addsite.AddSiteActivity import com.afollestad.nocknock.notifications.notificationsModule
import com.afollestad.nocknock.ui.main.MainActivity import com.afollestad.nocknock.utilities.utilitiesModule
import com.afollestad.nocknock.ui.viewsite.ViewSiteActivity import org.koin.android.ext.android.inject
import com.afollestad.nocknock.utilities.Injector import org.koin.android.ext.android.startKoin
import com.afollestad.nocknock.utilities.ext.systemService
import okhttp3.OkHttpClient
import timber.log.Timber import timber.log.Timber
import timber.log.Timber.DebugTree import timber.log.Timber.DebugTree
import javax.inject.Inject
import timber.log.Timber.d as log import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class NockNockApp : Application(), Injector { class NockNockApp : Application() {
private lateinit var appComponent: AppComponent
@Inject lateinit var nockNotificationManager: NockNotificationManager
private var resumedActivities: Int = 0 private var resumedActivities: Int = 0
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(DebugTree()) Timber.plant(DebugTree())
} }
val okHttpClient = OkHttpClient.Builder() val modules = listOf(
.addNetworkInterceptor { chain -> mainModule,
val request = chain.request() engineModule,
.newBuilder() utilitiesModule,
.addHeader("User-Agent", "com.afollestad.nocknock") notificationsModule,
.build() viewModelModule
chain.proceed(request) )
} startKoin(
.build() androidContext = this,
modules = modules
appComponent = DaggerAppComponent.builder() )
.application(this)
.okHttpClient(okHttpClient)
.jobScheduler(systemService(JOB_SCHEDULER_SERVICE))
.notificationManager(systemService(NOTIFICATION_SERVICE))
.build()
appComponent.inject(this)
val nockNotificationManager by inject<NockNotificationManager>()
onActivityLifeChange { activity, resumed -> onActivityLifeChange { activity, resumed ->
if (resumed) { if (resumed) {
resumedActivities++ resumedActivities++
@ -77,13 +64,4 @@ class NockNockApp : Application(), Injector {
nockNotificationManager.setIsAppOpen(resumedActivities > 0) nockNotificationManager.setIsAppOpen(resumedActivities > 0)
} }
} }
override fun injectInto(target: Any) = when (target) {
is MainActivity -> appComponent.inject(target)
is ViewSiteActivity -> appComponent.inject(target)
is AddSiteActivity -> appComponent.inject(target)
is ValidationJob -> appComponent.inject(target)
is BootReceiver -> appComponent.inject(target)
else -> throw IllegalStateException("Can't inject into $target")
}
} }

View file

@ -1,77 +0,0 @@
/*
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.di;
import android.app.Application;
import android.app.NotificationManager;
import android.app.job.JobScheduler;
import com.afollestad.nocknock.NockNockApp;
import com.afollestad.nocknock.di.viewmodels.ViewModelModule;
import com.afollestad.nocknock.engine.EngineModule;
import com.afollestad.nocknock.engine.validation.BootReceiver;
import com.afollestad.nocknock.engine.validation.ValidationJob;
import com.afollestad.nocknock.notifications.NotificationsModule;
import com.afollestad.nocknock.ui.addsite.AddSiteActivity;
import com.afollestad.nocknock.ui.main.MainActivity;
import com.afollestad.nocknock.ui.viewsite.ViewSiteActivity;
import com.afollestad.nocknock.utilities.UtilitiesModule;
import dagger.BindsInstance;
import dagger.Component;
import javax.inject.Singleton;
import okhttp3.OkHttpClient;
/** @author Aidan Follestad (@afollestad) */
@Singleton
@Component(
modules = {
MainModule.class,
ViewModelModule.class,
EngineModule.class,
NotificationsModule.class,
UtilitiesModule.class
}
)
public interface AppComponent {
void inject(NockNockApp app);
void inject(MainActivity activity);
void inject(ViewSiteActivity activity);
void inject(AddSiteActivity activity);
void inject(ValidationJob job);
void inject(BootReceiver bootReceiver);
@Component.Builder interface Builder {
@BindsInstance
Builder application(Application application);
@BindsInstance
Builder okHttpClient(OkHttpClient okHttpClient);
@BindsInstance
Builder jobScheduler(JobScheduler jobScheduler);
@BindsInstance
Builder notificationManager(NotificationManager notificationManager);
AppComponent build();
}
}

View file

@ -1,73 +0,0 @@
/*
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.di;
import android.app.Application;
import com.afollestad.nocknock.R;
import com.afollestad.nocknock.data.AppDatabase;
import com.afollestad.nocknock.ui.main.MainActivity;
import com.afollestad.nocknock.utilities.qualifiers.AppIconRes;
import com.afollestad.nocknock.di.qualifiers.IoDispatcher;
import com.afollestad.nocknock.utilities.qualifiers.MainActivityClass;
import com.afollestad.nocknock.di.qualifiers.MainDispatcher;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.Dispatchers;
import static androidx.room.Room.databaseBuilder;
/** @author Aidan Follestad (@afollestad) */
@Module
abstract class MainModule {
@SuppressWarnings("FieldCanBeLocal")
private static String DATABASE_NAME = "NockNock.db";
@Provides
@Singleton
@AppIconRes
static int provideAppIconRes() {
return R.mipmap.ic_launcher;
}
@Provides
@Singleton
@MainActivityClass
static Class<?> provideMainActivityClass() {
return MainActivity.class;
}
@Provides
@Singleton
static AppDatabase provideAppDatabase(Application app) {
return databaseBuilder(app, AppDatabase.class, DATABASE_NAME).build();
}
@Provides
@Singleton
@MainDispatcher
static CoroutineDispatcher provideMainDispatcher() {
return Dispatchers.getMain();
}
@Provides
@Singleton
@IoDispatcher
static CoroutineDispatcher provideIoDispatcher() {
return Dispatchers.getIO();
}
}

View file

@ -1,24 +0,0 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.di.qualifiers
import javax.inject.Qualifier
import kotlin.annotation.AnnotationRetention.RUNTIME
/** @author Aidan Follestad (@afollestad) */
@Qualifier
@Retention(RUNTIME)
annotation class IoDispatcher

View file

@ -1,24 +0,0 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.di.qualifiers
import javax.inject.Qualifier
import kotlin.annotation.AnnotationRetention.RUNTIME
/** @author Aidan Follestad (@afollestad) */
@Qualifier
@Retention(RUNTIME)
annotation class MainDispatcher

View file

@ -1,48 +0,0 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.di.viewmodels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.MapKey
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
import kotlin.reflect.KClass
typealias ViewModelMap = MutableMap<Class<out ViewModel>, Provider<ViewModel>>
@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
@Retention(RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
/**
* https://proandroiddev.com/viewmodel-with-dagger2-architecture-components-2e06f06c9455
*/
@Singleton
class ViewModelFactory @Inject constructor(private val viewModels: ViewModelMap) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return viewModels[modelClass]?.get() as T
}
}

View file

@ -1,33 +0,0 @@
package com.afollestad.nocknock.di.viewmodels;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.afollestad.nocknock.ui.addsite.AddSiteViewModel;
import com.afollestad.nocknock.ui.main.MainViewModel;
import com.afollestad.nocknock.ui.viewsite.ViewSiteViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
/** https://proandroiddev.com/viewmodel-with-dagger2-architecture-components-2e06f06c9455 */
@Module
public abstract class ViewModelModule {
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
@Binds
@IntoMap
@ViewModelKey(MainViewModel.class)
abstract ViewModel mainViewModel(MainViewModel viewModel);
@Binds
@IntoMap
@ViewModelKey(AddSiteViewModel.class)
abstract ViewModel addSiteViewModel(AddSiteViewModel viewModel);
@Binds
@IntoMap
@ViewModelKey(ViewSiteViewModel.class)
abstract ViewModel viewSiteViewModel(ViewSiteViewModel viewModel);
}

View file

@ -0,0 +1,63 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.koin
import android.app.Application
import android.app.NotificationManager
import android.app.job.JobScheduler
import android.content.Context.JOB_SCHEDULER_SERVICE
import android.content.Context.NOTIFICATION_SERVICE
import androidx.room.Room.databaseBuilder
import com.afollestad.nocknock.R
import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.notifications.Qualifiers.APP_ICON_RES
import com.afollestad.nocknock.ui.main.MainActivity
import com.afollestad.nocknock.utilities.Qualifiers.MAIN_ACTIVITY_CLASS
import com.afollestad.nocknock.utilities.ext.systemService
import okhttp3.OkHttpClient
import org.koin.dsl.module.module
const val MAIN_MODULE = "main"
/** @author Aidan Follestad (@afollestad) */
val mainModule = module(MAIN_MODULE) {
single(name = APP_ICON_RES) { R.mipmap.ic_launcher }
single(name = MAIN_ACTIVITY_CLASS) { MainActivity::class.java }
single { databaseBuilder(get(), AppDatabase::class.java, "NockNock.db").build() }
single {
OkHttpClient.Builder()
.addNetworkInterceptor { chain ->
val request = chain.request()
.newBuilder()
.addHeader("User-Agent", "com.afollestad.nocknock")
.build()
chain.proceed(request)
}
.build()
}
single<JobScheduler> {
get<Application>().systemService(JOB_SCHEDULER_SERVICE)
}
single<NotificationManager> {
get<Application>().systemService(NOTIFICATION_SERVICE)
}
}

View file

@ -0,0 +1,60 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.koin
import com.afollestad.nocknock.ui.addsite.AddSiteViewModel
import com.afollestad.nocknock.ui.main.MainViewModel
import com.afollestad.nocknock.ui.viewsite.ViewSiteViewModel
import com.afollestad.nocknock.utilities.Qualifiers.IO_DISPATCHER
import com.afollestad.nocknock.utilities.Qualifiers.MAIN_DISPATCHER
import org.koin.androidx.viewmodel.ext.koin.viewModel
import org.koin.dsl.module.module
const val VIEW_MODEL_MODULE = "view_models"
/** @author Aidan Follestad (@afollestad) */
val viewModelModule = module(VIEW_MODEL_MODULE) {
viewModel {
MainViewModel(
get(),
get(),
get(),
get(name = MAIN_DISPATCHER),
get(name = IO_DISPATCHER)
)
}
viewModel {
AddSiteViewModel(
get(),
get(),
get(name = MAIN_DISPATCHER),
get(name = IO_DISPATCHER)
)
}
viewModel {
ViewSiteViewModel(
get(),
get(),
get(),
get(),
get(name = MAIN_DISPATCHER),
get(name = IO_DISPATCHER)
)
}
}

View file

@ -13,21 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.afollestad.nocknock.di.viewmodels package com.afollestad.nocknock.ui
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.afollestad.nocknock.di.qualifiers.MainDispatcher
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import javax.inject.Inject
abstract class ScopedViewModel : ViewModel() { /** @author Aidan Follestad (@afollestad) */
abstract class ScopedViewModel(mainDispatcher: CoroutineDispatcher) : ViewModel() {
@Inject
@MainDispatcher
lateinit var mainDispatcher: CoroutineDispatcher
private val job = Job() private val job = Job()
protected val scope = CoroutineScope(job + mainDispatcher) protected val scope = CoroutineScope(job + mainDispatcher)

View file

@ -19,11 +19,8 @@ import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.afollestad.nocknock.R import com.afollestad.nocknock.R
import com.afollestad.nocknock.data.model.ValidationMode import com.afollestad.nocknock.data.model.ValidationMode
import com.afollestad.nocknock.utilities.ext.injector
import com.afollestad.nocknock.viewcomponents.ext.attachLiveData import com.afollestad.nocknock.viewcomponents.ext.attachLiveData
import com.afollestad.nocknock.viewcomponents.ext.conceal import com.afollestad.nocknock.viewcomponents.ext.conceal
import com.afollestad.nocknock.viewcomponents.ext.onLayout import com.afollestad.nocknock.viewcomponents.ext.onLayout
@ -43,7 +40,7 @@ import kotlinx.android.synthetic.main.activity_addsite.scriptInputLayout
import kotlinx.android.synthetic.main.activity_addsite.textUrlWarning import kotlinx.android.synthetic.main.activity_addsite.textUrlWarning
import kotlinx.android.synthetic.main.activity_addsite.toolbar import kotlinx.android.synthetic.main.activity_addsite.toolbar
import kotlinx.android.synthetic.main.activity_addsite.validationModeDescription import kotlinx.android.synthetic.main.activity_addsite.validationModeDescription
import javax.inject.Inject import org.koin.androidx.viewmodel.ext.android.viewModel
import kotlin.math.max import kotlin.math.max
import kotlin.properties.Delegates.notNull import kotlin.properties.Delegates.notNull
@ -58,19 +55,13 @@ class AddSiteActivity : AppCompatActivity() {
var revealCy by notNull<Int>() var revealCy by notNull<Int>()
var revealRadius by notNull<Float>() var revealRadius by notNull<Float>()
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
internal var isClosing = false internal var isClosing = false
private val viewModel by lazy {
return@lazy ViewModelProviders.of(this, viewModelFactory) private val viewModel by viewModel<AddSiteViewModel>()
.get(AddSiteViewModel::class.java)
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
injector().injectInto(this)
setContentView(R.layout.activity_addsite) setContentView(R.layout.activity_addsite)
setupUi(savedInstanceState) setupUi(savedInstanceState)

View file

@ -28,23 +28,22 @@ import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.data.putSite import com.afollestad.nocknock.data.putSite
import com.afollestad.nocknock.di.viewmodels.ScopedViewModel
import com.afollestad.nocknock.engine.validation.ValidationManager import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.di.qualifiers.IoDispatcher import com.afollestad.nocknock.ui.ScopedViewModel
import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan
import com.afollestad.nocknock.viewcomponents.ext.map import com.afollestad.nocknock.viewcomponents.ext.map
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.HttpUrl import okhttp3.HttpUrl
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class AddSiteViewModel @Inject constructor( class AddSiteViewModel(
private val database: AppDatabase, private val database: AppDatabase,
private val validationManager: ValidationManager, private val validationManager: ValidationManager,
@field:IoDispatcher private val ioDispatcher: CoroutineDispatcher mainDispatcher: CoroutineDispatcher,
) : ScopedViewModel(), LifecycleObserver { private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
// Public properties // Public properties
val name = MutableLiveData<String>() val name = MutableLiveData<String>()

View file

@ -19,8 +19,6 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -28,31 +26,27 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems import com.afollestad.materialdialogs.list.listItems
import com.afollestad.nocknock.R import com.afollestad.nocknock.R
import com.afollestad.nocknock.adapter.ServerAdapter import com.afollestad.nocknock.adapter.ServerAdapter
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.dialogs.AboutDialog import com.afollestad.nocknock.dialogs.AboutDialog
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.utilities.ext.injector
import com.afollestad.nocknock.viewcomponents.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.fab
import kotlinx.android.synthetic.main.activity_main.list import kotlinx.android.synthetic.main.activity_main.list
import kotlinx.android.synthetic.main.activity_main.loadingProgress import kotlinx.android.synthetic.main.activity_main.loadingProgress
import kotlinx.android.synthetic.main.activity_main.toolbar import kotlinx.android.synthetic.main.activity_main.toolbar
import kotlinx.android.synthetic.main.include_empty_view.emptyText import kotlinx.android.synthetic.main.include_empty_view.emptyText
import javax.inject.Inject import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@Inject lateinit var notificationManager: NockNotificationManager private val notificationManager by inject<NockNotificationManager>()
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory internal val viewModel by viewModel<MainViewModel>()
private lateinit var adapter: ServerAdapter private lateinit var adapter: ServerAdapter
internal val viewModel by lazy {
return@lazy ViewModelProviders.of(this, viewModelFactory)
.get(MainViewModel::class.java)
}
private val statusUpdateReceiver = private val statusUpdateReceiver =
StatusUpdateIntentReceiver(application) { StatusUpdateIntentReceiver(application) {
viewModel.postSiteUpdate(it) viewModel.postSiteUpdate(it)
@ -60,7 +54,6 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
injector().injectInto(this)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
setupUi() setupUi()

View file

@ -25,22 +25,21 @@ import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.allSites import com.afollestad.nocknock.data.allSites
import com.afollestad.nocknock.data.deleteSite import com.afollestad.nocknock.data.deleteSite
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.di.viewmodels.ScopedViewModel
import com.afollestad.nocknock.engine.validation.ValidationManager import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.di.qualifiers.IoDispatcher import com.afollestad.nocknock.ui.ScopedViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class MainViewModel @Inject constructor( class MainViewModel(
private val database: AppDatabase, private val database: AppDatabase,
private val notificationManager: NockNotificationManager, private val notificationManager: NockNotificationManager,
private val validationManager: ValidationManager, private val validationManager: ValidationManager,
@field:IoDispatcher private val ioDispatcher: CoroutineDispatcher mainDispatcher: CoroutineDispatcher,
) : ScopedViewModel(), LifecycleObserver { private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
private val sites = MutableLiveData<List<Site>>() private val sites = MutableLiveData<List<Site>>()
private val isLoading = MutableLiveData<Boolean>() private val isLoading = MutableLiveData<Boolean>()

View file

@ -21,13 +21,10 @@ import android.os.Bundle
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.afollestad.nocknock.R import com.afollestad.nocknock.R
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.ValidationMode import com.afollestad.nocknock.data.model.ValidationMode
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.utilities.ext.injector
import com.afollestad.nocknock.viewcomponents.ext.attachLiveData import com.afollestad.nocknock.viewcomponents.ext.attachLiveData
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
import com.afollestad.nocknock.viewcomponents.ext.onScroll import com.afollestad.nocknock.viewcomponents.ext.onScroll
@ -51,17 +48,13 @@ import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck
import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning
import kotlinx.android.synthetic.main.activity_viewsite.toolbar import kotlinx.android.synthetic.main.activity_viewsite.toolbar
import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription
import javax.inject.Inject import org.koin.androidx.viewmodel.ext.android.viewModel
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class ViewSiteActivity : AppCompatActivity() { class ViewSiteActivity : AppCompatActivity() {
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory internal val viewModel by viewModel<ViewSiteViewModel>()
internal val viewModel by lazy {
return@lazy ViewModelProviders.of(this, viewModelFactory)
.get(ViewSiteViewModel::class.java)
}
private val statusUpdateReceiver = private val statusUpdateReceiver =
StatusUpdateIntentReceiver(application) { StatusUpdateIntentReceiver(application) {
viewModel.setModel(it) viewModel.setModel(it)
@ -70,8 +63,6 @@ class ViewSiteActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
injector().injectInto(this)
setContentView(R.layout.activity_viewsite) setContentView(R.layout.activity_viewsite)
setupUi() setupUi()

View file

@ -15,7 +15,6 @@
*/ */
package com.afollestad.nocknock.ui.viewsite package com.afollestad.nocknock.ui.viewsite
import android.app.Application
import androidx.annotation.CheckResult import androidx.annotation.CheckResult
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@ -33,11 +32,11 @@ import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.data.model.ValidationResult import com.afollestad.nocknock.data.model.ValidationResult
import com.afollestad.nocknock.data.model.textRes import com.afollestad.nocknock.data.model.textRes
import com.afollestad.nocknock.data.updateSite import com.afollestad.nocknock.data.updateSite
import com.afollestad.nocknock.di.viewmodels.ScopedViewModel
import com.afollestad.nocknock.engine.validation.ValidationManager import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.ui.ScopedViewModel
import com.afollestad.nocknock.utilities.ext.formatDate import com.afollestad.nocknock.utilities.ext.formatDate
import com.afollestad.nocknock.di.qualifiers.IoDispatcher import com.afollestad.nocknock.utilities.providers.StringProvider
import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan
import com.afollestad.nocknock.viewcomponents.ext.map import com.afollestad.nocknock.viewcomponents.ext.map
import com.afollestad.nocknock.viewcomponents.ext.zip import com.afollestad.nocknock.viewcomponents.ext.zip
@ -46,16 +45,16 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.HttpUrl import okhttp3.HttpUrl
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class ViewSiteViewModel @Inject constructor( class ViewSiteViewModel(
private val app: Application, private val stringProvider: StringProvider,
private val database: AppDatabase, private val database: AppDatabase,
private val notificationManager: NockNotificationManager, private val notificationManager: NockNotificationManager,
private val validationManager: ValidationManager, private val validationManager: ValidationManager,
@field:IoDispatcher private val ioDispatcher: CoroutineDispatcher mainDispatcher: CoroutineDispatcher,
) : ScopedViewModel(), LifecycleObserver { private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
lateinit var site: Site lateinit var site: Site
@ -134,13 +133,13 @@ class ViewSiteViewModel @Inject constructor(
@CheckResult fun onLastCheckResultText(): LiveData<String> = lastResult.map { @CheckResult fun onLastCheckResultText(): LiveData<String> = lastResult.map {
if (it == null) { if (it == null) {
app.getString(R.string.none) stringProvider.get(R.string.none)
} else { } else {
val statusText = it.status.textRes() val statusText = it.status.textRes()
if (statusText == 0) { if (statusText == 0) {
it.reason it.reason
} else { } else {
app.getString(statusText) stringProvider.get(statusText)
} }
} }
} }
@ -151,7 +150,7 @@ class ViewSiteViewModel @Inject constructor(
val disabled = it.first val disabled = it.first
val lastResult = it.second val lastResult = it.second
if (disabled) { if (disabled) {
app.getString(R.string.auto_checks_disabled) stringProvider.get(R.string.auto_checks_disabled)
} else { } else {
val lastCheck = lastResult?.timestampMs ?: currentTimeMillis() val lastCheck = lastResult?.timestampMs ?: currentTimeMillis()
(lastCheck + getCheckIntervalMs()).formatDate() (lastCheck + getCheckIntervalMs()).formatDate()

View file

@ -1,8 +1,24 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock package com.afollestad.nocknock
import androidx.annotation.CheckResult import androidx.annotation.CheckResult
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage import com.google.common.truth.Truth.assertWithMessage
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
@ -22,12 +38,11 @@ class TestLiveData<T>(data: LiveData<T>) {
fun assertValues(vararg assertValues: T) { fun assertValues(vararg assertValues: T) {
val assertList = assertValues.toList() val assertList = assertValues.toList()
assertWithMessage("Expected: $assertList, but got: $receivedValues").that(receivedValues) assertThat(receivedValues).isEqualTo(assertList)
.isEqualTo(assertList)
receivedValues.clear() receivedValues.clear()
} }
@CheckResult fun values(): List<T> = receivedValues @CheckResult fun values(): List<T> = receivedValues
} }
@CheckResult fun <T> LiveData<T>.test() = TestLiveData(this) @CheckResult fun <T> LiveData<T>.test() = TestLiveData(this)

View file

@ -1,3 +1,18 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.broadcasts package com.afollestad.nocknock.broadcasts
import android.app.Application import android.app.Application
@ -52,4 +67,4 @@ class StatusUpdateIntentReceiverTest {
receiver.onPause() receiver.onPause()
verify(app).unregisterReceiver(receiver.intentReceiver) verify(app).unregisterReceiver(receiver.intentReceiver)
} }
} }

View file

@ -42,7 +42,7 @@ package com.afollestad.nocknock.ui.addsite
//import org.junit.Before //import org.junit.Before
//import org.junit.Test //import org.junit.Test
// //
//class AddSitePresenterTest { //class AddSiteViewModelTest {
// //
// private val database = mockDatabase() // private val database = mockDatabase()
// private val checkStatusManager = mock<ValidationManager>() // private val checkStatusManager = mock<ValidationManager>()

View file

@ -15,6 +15,7 @@
*/ */
package com.afollestad.nocknock.ui.main package com.afollestad.nocknock.ui.main
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.afollestad.nocknock.ALL_MOCK_MODELS import com.afollestad.nocknock.ALL_MOCK_MODELS
import com.afollestad.nocknock.MOCK_MODEL_1 import com.afollestad.nocknock.MOCK_MODEL_1
import com.afollestad.nocknock.MOCK_MODEL_2 import com.afollestad.nocknock.MOCK_MODEL_2
@ -28,22 +29,25 @@ import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.After import org.junit.After
import org.junit.Rule
import org.junit.Test import org.junit.Test
/** @author Aidan Follestad (@afollestad) */
class MainViewModelTest { class MainViewModelTest {
private val database = mockDatabase() private val database = mockDatabase()
private val notificationManager = mock<NockNotificationManager>() private val notificationManager = mock<NockNotificationManager>()
private val validationManager = mock<ValidationManager>() private val validationManager = mock<ValidationManager>()
@Rule @JvmField val rule = InstantTaskExecutorRule()
private val viewModel = MainViewModel( private val viewModel = MainViewModel(
database, database,
notificationManager, notificationManager,
validationManager, validationManager,
Dispatchers.Default Dispatchers.Unconfined,
).apply { Dispatchers.Unconfined
this.mainDispatcher = Dispatchers.Default )
}
@After fun tearDown() = viewModel.destroy() @After fun tearDown() = viewModel.destroy()
@ -92,7 +96,6 @@ class MainViewModelTest {
viewModel.postSiteUpdate(updatedModel2) viewModel.postSiteUpdate(updatedModel2)
sites.assertValues(updatedSites) sites.assertValues(updatedSites)
} }
@Test fun refreshSite() { @Test fun refreshSite() {
@ -116,6 +119,7 @@ class MainViewModelTest {
listOf(), listOf(),
ALL_MOCK_MODELS ALL_MOCK_MODELS
) )
isLoading.assertValues(true, false)
val modifiedModel = MOCK_MODEL_1.copy(id = 11111) val modifiedModel = MOCK_MODEL_1.copy(id = 11111)
viewModel.removeSite(modifiedModel) viewModel.removeSite(modifiedModel)
@ -140,6 +144,7 @@ class MainViewModelTest {
listOf(), listOf(),
ALL_MOCK_MODELS ALL_MOCK_MODELS
) )
isLoading.assertValues(true, false)
val modelsWithout1 = ALL_MOCK_MODELS.toMutableList() val modelsWithout1 = ALL_MOCK_MODELS.toMutableList()
.apply { .apply {

View file

@ -15,6 +15,6 @@
*/ */
package com.afollestad.nocknock.ui.viewsite package com.afollestad.nocknock.ui.viewsite
class ViewSitePresenterTest { class ViewSiteViewModelTest
} // TODO this will be mostly identical to the add site test

View file

@ -26,9 +26,7 @@ dependencies {
api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:' + versions.coroutines api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:' + versions.coroutines
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + versions.coroutines api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + versions.coroutines
implementation 'com.google.dagger:dagger:' + versions.dagger implementation 'org.koin:koin-android:' + versions.koin
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
implementation 'org.mozilla:rhino:' + versions.rhino implementation 'org.mozilla:rhino:' + versions.rhino
testImplementation 'junit:junit:' + versions.junit testImplementation 'junit:junit:' + versions.junit

View file

@ -1,11 +1,11 @@
/* /**
* Designed and developed by Aidan Follestad (@afollestad) * Designed and developed by Aidan Follestad (@afollestad)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -13,63 +13,62 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.afollestad.nocknock.utilities; package com.afollestad.nocknock.utilities
import com.afollestad.nocknock.utilities.providers.BitmapProvider; import com.afollestad.nocknock.utilities.Qualifiers.IO_DISPATCHER
import com.afollestad.nocknock.utilities.providers.BundleProvider; import com.afollestad.nocknock.utilities.Qualifiers.MAIN_ACTIVITY_CLASS
import com.afollestad.nocknock.utilities.providers.IntentProvider; import com.afollestad.nocknock.utilities.Qualifiers.MAIN_DISPATCHER
import com.afollestad.nocknock.utilities.providers.JobInfoProvider; import com.afollestad.nocknock.utilities.providers.BitmapProvider
import com.afollestad.nocknock.utilities.providers.NotificationChannelProvider; import com.afollestad.nocknock.utilities.providers.BundleProvider
import com.afollestad.nocknock.utilities.providers.NotificationProvider; import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.afollestad.nocknock.utilities.providers.RealBitmapProvider; import com.afollestad.nocknock.utilities.providers.JobInfoProvider
import com.afollestad.nocknock.utilities.providers.RealBundleProvider; import com.afollestad.nocknock.utilities.providers.NotificationChannelProvider
import com.afollestad.nocknock.utilities.providers.RealIntentProvider; import com.afollestad.nocknock.utilities.providers.NotificationProvider
import com.afollestad.nocknock.utilities.providers.RealJobInfoProvider; import com.afollestad.nocknock.utilities.providers.RealBitmapProvider
import com.afollestad.nocknock.utilities.providers.RealNotificationChannelProvider; import com.afollestad.nocknock.utilities.providers.RealBundleProvider
import com.afollestad.nocknock.utilities.providers.RealNotificationProvider; import com.afollestad.nocknock.utilities.providers.RealIntentProvider
import com.afollestad.nocknock.utilities.providers.RealSdkProvider; import com.afollestad.nocknock.utilities.providers.RealJobInfoProvider
import com.afollestad.nocknock.utilities.providers.RealStringProvider; import com.afollestad.nocknock.utilities.providers.RealNotificationChannelProvider
import com.afollestad.nocknock.utilities.providers.SdkProvider; import com.afollestad.nocknock.utilities.providers.RealNotificationProvider
import com.afollestad.nocknock.utilities.providers.StringProvider; import com.afollestad.nocknock.utilities.providers.RealSdkProvider
import dagger.Binds; import com.afollestad.nocknock.utilities.providers.RealStringProvider
import dagger.Module; import com.afollestad.nocknock.utilities.providers.SdkProvider
import javax.inject.Singleton; import com.afollestad.nocknock.utilities.providers.StringProvider
import kotlinx.coroutines.Dispatchers
import org.koin.dsl.module.module
const val UTILITIES_MODULE = "utilities"
object Qualifiers {
const val MAIN_ACTIVITY_CLASS = "main.main_activity_class"
const val MAIN_DISPATCHER = "main.main_dispatcher"
const val IO_DISPATCHER = "main.io_dispatcher"
}
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
@Module val utilitiesModule = module(UTILITIES_MODULE) {
public abstract class UtilitiesModule {
@Binds factory(name = MAIN_DISPATCHER) { Dispatchers.Main }
@Singleton
abstract SdkProvider provideSdkProvider(RealSdkProvider sdkProvider);
@Binds factory(name = IO_DISPATCHER) { Dispatchers.IO }
@Singleton
abstract BitmapProvider provideBitmapProvider(RealBitmapProvider bitmapProvider);
@Binds factory { RealSdkProvider() } bind SdkProvider::class
@Singleton
abstract StringProvider provideStringProvider(RealStringProvider stringProvider);
@Binds factory { RealBitmapProvider(get()) } bind BitmapProvider::class
@Singleton
abstract IntentProvider provideIntentProvider(RealIntentProvider intentProvider);
@Binds factory { RealStringProvider(get()) } bind StringProvider::class
@Singleton
abstract NotificationChannelProvider provideChannelProvider(
RealNotificationChannelProvider channelProvider);
@Binds factory {
@Singleton RealIntentProvider(get(), get(name = MAIN_ACTIVITY_CLASS))
abstract NotificationProvider provideNotificationProvider( } bind IntentProvider::class
RealNotificationProvider notificationProvider);
@Binds factory {
@Singleton RealNotificationChannelProvider(get())
abstract BundleProvider provideBundleProvider(RealBundleProvider bundleProvider); } bind NotificationChannelProvider::class
@Binds factory { RealNotificationProvider(get()) } bind NotificationProvider::class
@Singleton
abstract JobInfoProvider provideJobInfoProvider(RealJobInfoProvider jobInfoProvider); factory { RealBundleProvider() } bind BundleProvider::class
factory { RealJobInfoProvider(get()) } bind JobInfoProvider::class
} }

View file

@ -15,11 +15,10 @@
*/ */
package com.afollestad.nocknock.utilities.providers package com.afollestad.nocknock.utilities.providers
import android.app.Application import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory.decodeResource import android.graphics.BitmapFactory.decodeResource
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface BitmapProvider { interface BitmapProvider {
@ -28,11 +27,11 @@ interface BitmapProvider {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealBitmapProvider @Inject constructor( class RealBitmapProvider(
private val app: Application private val context: Context
) : BitmapProvider { ) : BitmapProvider {
override fun get(res: Int): Bitmap { override fun get(res: Int): Bitmap {
return decodeResource(app.resources, res) return decodeResource(context.resources, res)
} }
} }

View file

@ -16,7 +16,6 @@
package com.afollestad.nocknock.utilities.providers package com.afollestad.nocknock.utilities.providers
import android.os.PersistableBundle import android.os.PersistableBundle
import javax.inject.Inject
interface IBundle { interface IBundle {
fun putLong( fun putLong(
@ -34,7 +33,7 @@ interface BundleProvider {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealBundleProvider @Inject constructor() : BundleProvider { class RealBundleProvider : BundleProvider {
override fun createPersistable(bundler: IBundler): PersistableBundle { override fun createPersistable(bundler: IBundler): PersistableBundle {
val realBundle = PersistableBundle() val realBundle = PersistableBundle()

View file

@ -15,13 +15,11 @@
*/ */
package com.afollestad.nocknock.utilities.providers package com.afollestad.nocknock.utilities.providers
import android.app.Application
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.content.Context
import android.content.Intent import android.content.Intent
import com.afollestad.nocknock.utilities.qualifiers.MainActivityClass
import java.io.Serializable import java.io.Serializable
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface CanNotifyModel : Serializable { interface CanNotifyModel : Serializable {
@ -42,9 +40,9 @@ interface IntentProvider {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealIntentProvider @Inject constructor( class RealIntentProvider(
private val app: Application, private val context: Context,
@MainActivityClass private val mainActivity: Class<*> private val mainActivityClass: Class<*>
) : IntentProvider { ) : IntentProvider {
companion object { companion object {
@ -55,7 +53,7 @@ class RealIntentProvider @Inject constructor(
override fun getPendingIntentForViewSite(model: CanNotifyModel): PendingIntent { override fun getPendingIntentForViewSite(model: CanNotifyModel): PendingIntent {
val openIntent = getIntentForViewSite(model) val openIntent = getIntentForViewSite(model)
return PendingIntent.getActivity( return PendingIntent.getActivity(
app, context,
BASE_NOTIFICATION_REQUEST_CODE + model.notiId(), BASE_NOTIFICATION_REQUEST_CODE + model.notiId(),
openIntent, openIntent,
FLAG_CANCEL_CURRENT FLAG_CANCEL_CURRENT
@ -63,7 +61,7 @@ class RealIntentProvider @Inject constructor(
} }
private fun getIntentForViewSite(model: CanNotifyModel) = private fun getIntentForViewSite(model: CanNotifyModel) =
Intent(app, mainActivity).apply { Intent(context, mainActivityClass).apply {
putExtra(KEY_VIEW_NOTIFICATION_MODEL, model) putExtra(KEY_VIEW_NOTIFICATION_MODEL, model)
} }
} }

View file

@ -15,13 +15,12 @@
*/ */
package com.afollestad.nocknock.utilities.providers package com.afollestad.nocknock.utilities.providers
import android.app.Application
import android.app.job.JobInfo import android.app.job.JobInfo
import android.app.job.JobInfo.NETWORK_TYPE_ANY import android.app.job.JobInfo.NETWORK_TYPE_ANY
import android.app.job.JobInfo.NETWORK_TYPE_UNMETERED import android.app.job.JobInfo.NETWORK_TYPE_UNMETERED
import android.content.ComponentName import android.content.ComponentName
import android.content.Context
import android.os.PersistableBundle import android.os.PersistableBundle
import javax.inject.Inject
interface JobInfoProvider { interface JobInfoProvider {
@ -34,8 +33,8 @@ interface JobInfoProvider {
): JobInfo ): JobInfo
} }
class RealJobInfoProvider @Inject constructor( class RealJobInfoProvider(
private val app: Application private val context: Context
) : JobInfoProvider { ) : JobInfoProvider {
// Note: we don't use the periodic feature of JobScheduler because it requires a // Note: we don't use the periodic feature of JobScheduler because it requires a
@ -48,7 +47,7 @@ class RealJobInfoProvider @Inject constructor(
extras: PersistableBundle, extras: PersistableBundle,
target: Class<*> target: Class<*>
): JobInfo { ): JobInfo {
val component = ComponentName(app, target) val component = ComponentName(context, target)
val networkType = if (onlyUnmeteredNetwork) { val networkType = if (onlyUnmeteredNetwork) {
NETWORK_TYPE_UNMETERED NETWORK_TYPE_UNMETERED
} else { } else {

View file

@ -18,7 +18,6 @@ package com.afollestad.nocknock.utilities.providers
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.NotificationChannel import android.app.NotificationChannel
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface NotificationChannelProvider { interface NotificationChannelProvider {
@ -33,7 +32,7 @@ interface NotificationChannelProvider {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealNotificationChannelProvider @Inject constructor( class RealNotificationChannelProvider(
private val sdkProvider: SdkProvider private val sdkProvider: SdkProvider
) : NotificationChannelProvider { ) : NotificationChannelProvider {

View file

@ -15,13 +15,12 @@
*/ */
package com.afollestad.nocknock.utilities.providers package com.afollestad.nocknock.utilities.providers
import android.app.Application
import android.app.Notification import android.app.Notification
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface NotificationProvider { interface NotificationProvider {
@ -37,8 +36,8 @@ interface NotificationProvider {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealNotificationProvider @Inject constructor( class RealNotificationProvider(
private val app: Application private val context: Context
) : NotificationProvider { ) : NotificationProvider {
override fun create( override fun create(
@ -49,7 +48,7 @@ class RealNotificationProvider @Inject constructor(
smallIcon: Int, smallIcon: Int,
largeIcon: Bitmap largeIcon: Bitmap
): Notification { ): Notification {
return NotificationCompat.Builder(app, channelId) return NotificationCompat.Builder(context, channelId)
.setContentTitle(title) .setContentTitle(title)
.setContentText(content) .setContentText(content)
.setContentIntent(intent) .setContentIntent(intent)

View file

@ -17,7 +17,6 @@ package com.afollestad.nocknock.utilities.providers
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O import android.os.Build.VERSION_CODES.O
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface SdkProvider { interface SdkProvider {
@ -26,7 +25,7 @@ interface SdkProvider {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealSdkProvider @Inject constructor() : SdkProvider { class RealSdkProvider : SdkProvider {
override fun hasOreo() = SDK_INT >= O override fun hasOreo() = SDK_INT >= O
} }

View file

@ -15,9 +15,8 @@
*/ */
package com.afollestad.nocknock.utilities.providers package com.afollestad.nocknock.utilities.providers
import android.app.Application import android.content.Context
import androidx.annotation.StringRes import androidx.annotation.StringRes
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface StringProvider { interface StringProvider {
@ -26,11 +25,11 @@ interface StringProvider {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealStringProvider @Inject constructor( class RealStringProvider(
private val app: Application private val context: Context
) : StringProvider { ) : StringProvider {
override fun get(res: Int): String { override fun get(res: Int): String {
return app.resources.getString(res) return context.resources.getString(res)
} }
} }

View file

@ -17,13 +17,10 @@ android {
} }
dependencies { dependencies {
implementation project(':utilities') implementation project(':common')
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin
implementation 'com.google.dagger:dagger:' + versions.dagger
annotationProcessor 'com.google.dagger:dagger-compiler:' + versions.dagger
api 'androidx.room:room-runtime:' + versions.room api 'androidx.room:room-runtime:' + versions.room
kapt 'androidx.room:room-compiler:' + versions.room kapt 'androidx.room:room-compiler:' + versions.room

View file

@ -12,9 +12,9 @@ ext.versions = [
okHttp : '3.12.0', okHttp : '3.12.0',
rhino : '1.7.10', rhino : '1.7.10',
dagger : '2.19',
kotlin : '1.3.10', kotlin : '1.3.10',
coroutines : '1.0.1', coroutines : '1.0.1',
koin : '1.0.2',
androidx : '1.0.0', androidx : '1.0.0',
room : '2.0.0', room : '2.0.0',
@ -30,6 +30,8 @@ ext.versions = [
mockito : '2.23.0', mockito : '2.23.0',
mockitoKotlin : '2.0.0-RC1', mockitoKotlin : '2.0.0-RC1',
truth : '0.42', truth : '0.42',
androidxTestRunner: '1.1.0', androidxTestRunner: '1.1.0',
androidxTest : '1.0.0', androidxTest : '1.0.0',
archTesting : '2.0.0'
] ]

View file

@ -14,7 +14,7 @@ android {
} }
dependencies { dependencies {
implementation project(':utilities') implementation project(':common')
implementation project(':data') implementation project(':data')
implementation project(':notifications') implementation project(':notifications')
@ -22,12 +22,12 @@ dependencies {
api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:' + versions.coroutines api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:' + versions.coroutines
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + versions.coroutines api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + versions.coroutines
implementation 'org.koin:koin-android:' + versions.koin
api 'com.squareup.okhttp3:okhttp:' + versions.okHttp api 'com.squareup.okhttp3:okhttp:' + versions.okHttp
implementation 'com.google.dagger:dagger:' + versions.dagger
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
implementation 'com.jakewharton.timber:timber:' + versions.timber implementation 'com.jakewharton.timber:timber:' + versions.timber
testImplementation 'junit:junit:' + versions.junit testImplementation 'junit:junit:' + versions.junit
testImplementation 'org.mockito:mockito-core:' + versions.mockito testImplementation 'org.mockito:mockito-core:' + versions.mockito
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin

View file

@ -1,4 +1,4 @@
/* /**
* Designed and developed by Aidan Follestad (@afollestad) * Designed and developed by Aidan Follestad (@afollestad)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -13,19 +13,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.afollestad.nocknock.engine; package com.afollestad.nocknock.engine
import com.afollestad.nocknock.engine.validation.RealValidationManager; import com.afollestad.nocknock.engine.validation.RealValidationManager
import com.afollestad.nocknock.engine.validation.ValidationManager; import com.afollestad.nocknock.engine.validation.ValidationManager
import dagger.Binds; import org.koin.dsl.module.module
import dagger.Module;
import javax.inject.Singleton; const val ENGINE_MODULE = "engine"
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
@Module val engineModule = module(ENGINE_MODULE) {
public abstract class EngineModule {
@Binds single {
@Singleton RealValidationManager(get(), get(), get(), get(), get(), get())
abstract ValidationManager provideValidationManager(RealValidationManager checkStatusManager); } bind ValidationManager::class
} }

View file

@ -19,19 +19,22 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.ACTION_BOOT_COMPLETED import android.content.Intent.ACTION_BOOT_COMPLETED
import com.afollestad.nocknock.utilities.ext.injector import com.afollestad.nocknock.utilities.Qualifiers.IO_DISPATCHER
import kotlinx.coroutines.Dispatchers.IO import com.afollestad.nocknock.utilities.Qualifiers.MAIN_DISPATCHER
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import kotlinx.coroutines.withContext
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject
import timber.log.Timber.d as log import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver(), KoinComponent {
@Inject lateinit var checkStatusManager: ValidationManager private val validationManager by inject<ValidationManager>()
private val mainDispatcher by inject<CoroutineDispatcher>(name = MAIN_DISPATCHER)
private val ioDispatcher by inject<CoroutineDispatcher>(name = IO_DISPATCHER)
override fun onReceive( override fun onReceive(
context: Context, context: Context,
@ -42,12 +45,10 @@ class BootReceiver : BroadcastReceiver() {
} }
log("Received boot event! Let's go.") log("Received boot event! Let's go.")
context.injector()
.injectInto(this)
val pendingResult = goAsync() val pendingResult = goAsync()
GlobalScope.launch(Main) { GlobalScope.launch(mainDispatcher) {
async(IO) { checkStatusManager.ensureScheduledChecks() }.await() withContext(ioDispatcher) { validationManager.ensureScheduledChecks() }
pendingResult.resultCode = 0 pendingResult.resultCode = 0
pendingResult.finish() pendingResult.finish()
} }

View file

@ -19,6 +19,7 @@ import android.app.job.JobParameters
import android.app.job.JobService import android.app.job.JobService
import android.content.Intent import android.content.Intent
import com.afollestad.nocknock.data.AppDatabase import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.getSite
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.Status import com.afollestad.nocknock.data.model.Status
import com.afollestad.nocknock.data.model.Status.CHECKING import com.afollestad.nocknock.data.model.Status.CHECKING
@ -29,11 +30,9 @@ import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.data.model.isPending import com.afollestad.nocknock.data.model.isPending
import com.afollestad.nocknock.data.getSite
import com.afollestad.nocknock.data.updateSite import com.afollestad.nocknock.data.updateSite
import com.afollestad.nocknock.engine.BuildConfig.APPLICATION_ID import com.afollestad.nocknock.engine.BuildConfig.APPLICATION_ID
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.utilities.ext.injector
import com.afollestad.nocknock.utilities.js.JavaScript import com.afollestad.nocknock.utilities.js.JavaScript
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
@ -41,8 +40,8 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import javax.inject.Inject
import timber.log.Timber.d as log import timber.log.Timber.d as log
/** /**
@ -59,12 +58,11 @@ class ValidationJob : JobService() {
const val KEY_SITE_ID = "site.id" const val KEY_SITE_ID = "site.id"
} }
@Inject lateinit var database: AppDatabase private val database by inject<AppDatabase>()
@Inject lateinit var checkStatusManager: ValidationManager private val validaitonManager by inject<ValidationManager>()
@Inject lateinit var notificationManager: NockNotificationManager private val notificationManager by inject<NockNotificationManager>()
override fun onStartJob(params: JobParameters): Boolean { override fun onStartJob(params: JobParameters): Boolean {
injector().injectInto(this)
val siteId = params.extras.getLong(KEY_SITE_ID) val siteId = params.extras.getLong(KEY_SITE_ID)
GlobalScope.launch(Main) { GlobalScope.launch(Main) {
@ -84,7 +82,7 @@ class ValidationJob : JobService() {
val jobResult = async(IO) { val jobResult = async(IO) {
updateStatus(site, CHECKING) updateStatus(site, CHECKING)
val checkResult = checkStatusManager.performCheck(site) val checkResult = validaitonManager.performCheck(site)
val resultModel = checkResult.model val resultModel = checkResult.model
val resultResponse = checkResult.response val resultResponse = checkResult.response
val result = resultModel.lastResult!! val result = resultModel.lastResult!!
@ -144,7 +142,7 @@ class ValidationJob : JobService() {
notificationManager.postStatusNotification(jobResult) notificationManager.postStatusNotification(jobResult)
} }
checkStatusManager.scheduleCheck( validaitonManager.scheduleCheck(
site = jobResult, site = jobResult,
fromFinishingJob = true fromFinishingJob = true
) )

View file

@ -33,7 +33,6 @@ import okhttp3.Response
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
import timber.log.Timber.d as log import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
@ -61,7 +60,7 @@ interface ValidationManager {
suspend fun performCheck(site: Site): CheckResult suspend fun performCheck(site: Site): CheckResult
} }
class RealValidationManager @Inject constructor( class RealValidationManager(
private val jobScheduler: JobScheduler, private val jobScheduler: JobScheduler,
private val okHttpClient: OkHttpClient, private val okHttpClient: OkHttpClient,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,

View file

@ -14,7 +14,7 @@ android {
} }
dependencies { dependencies {
implementation project(':utilities') implementation project(':common')
api 'androidx.appcompat:appcompat:' + versions.androidx api 'androidx.appcompat:appcompat:' + versions.androidx
@ -22,10 +22,10 @@ dependencies {
api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:' + versions.coroutines api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:' + versions.coroutines
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + versions.coroutines api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + versions.coroutines
implementation 'com.google.dagger:dagger:' + versions.dagger implementation 'org.koin:koin-android:' + versions.koin
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
implementation 'com.jakewharton.timber:timber:' + versions.timber implementation 'com.jakewharton.timber:timber:' + versions.timber
testImplementation 'junit:junit:' + versions.junit testImplementation 'junit:junit:' + versions.junit
testImplementation 'org.mockito:mockito-core:' + versions.mockito testImplementation 'org.mockito:mockito-core:' + versions.mockito
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin

View file

@ -26,8 +26,6 @@ import com.afollestad.nocknock.utilities.providers.NotificationChannelProvider
import com.afollestad.nocknock.utilities.providers.NotificationProvider import com.afollestad.nocknock.utilities.providers.NotificationProvider
import com.afollestad.nocknock.utilities.providers.RealIntentProvider.Companion.BASE_NOTIFICATION_REQUEST_CODE import com.afollestad.nocknock.utilities.providers.RealIntentProvider.Companion.BASE_NOTIFICATION_REQUEST_CODE
import com.afollestad.nocknock.utilities.providers.StringProvider import com.afollestad.nocknock.utilities.providers.StringProvider
import com.afollestad.nocknock.utilities.qualifiers.AppIconRes
import javax.inject.Inject
import timber.log.Timber.d as log import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
@ -45,8 +43,8 @@ interface NockNotificationManager {
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class RealNockNotificationManager @Inject constructor( class RealNockNotificationManager(
@AppIconRes private val appIconRes: Int, private val appIconRes: Int,
private val stockManager: NotificationManager, private val stockManager: NotificationManager,
private val bitmapProvider: BitmapProvider, private val bitmapProvider: BitmapProvider,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,

View file

@ -1,30 +0,0 @@
/*
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.notifications;
import dagger.Binds;
import dagger.Module;
import javax.inject.Singleton;
/** @author Aidan Follestad (@afollestad) */
@Module
public abstract class NotificationsModule {
@Binds
@Singleton
abstract NockNotificationManager provideNockNotificationManager(
RealNockNotificationManager notificationManager);
}

View file

@ -13,10 +13,28 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.afollestad.nocknock.utilities package com.afollestad.nocknock.notifications
/** @author Aidan Follestad (@afollestad)*/ import com.afollestad.nocknock.notifications.Qualifiers.APP_ICON_RES
interface Injector { import org.koin.dsl.module.module
fun injectInto(target: Any) const val NOTIFICATIONS_MODULE = "notifications"
object Qualifiers {
const val APP_ICON_RES = "main.app_icon_res"
}
val notificationsModule = module(NOTIFICATIONS_MODULE) {
single {
RealNockNotificationManager(
get(name = APP_ICON_RES),
get(),
get(),
get(),
get(),
get(),
get()
)
} bind NockNotificationManager::class
} }

View file

@ -1 +1 @@
include ':app', ':engine', ':notifications', ':data', ':utilities', ':viewcomponents' include ':app', ':engine', ':notifications', ':data', ':common', ':viewcomponents'

View file

@ -1,24 +0,0 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.utilities.ext
import android.app.job.JobService
import android.content.Context
import com.afollestad.nocknock.utilities.Injector
fun Context.injector() = applicationContext as Injector
fun JobService.injector() = applicationContext as Injector

View file

@ -1,24 +0,0 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.utilities.qualifiers
import javax.inject.Qualifier
import kotlin.annotation.AnnotationRetention.RUNTIME
/** @author Aidan Follestad (@afollestad) */
@Qualifier
@Retention(RUNTIME)
annotation class AppIconRes

View file

@ -1,24 +0,0 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.utilities.qualifiers
import javax.inject.Qualifier
import kotlin.annotation.AnnotationRetention.RUNTIME
/** @author Aidan Follestad (@afollestad) */
@Qualifier
@Retention(RUNTIME)
annotation class MainActivityClass

View file

@ -15,16 +15,13 @@ android {
} }
dependencies { dependencies {
implementation project(':utilities') implementation project(':common')
implementation project(':data') implementation project(':data')
implementation 'androidx.appcompat:appcompat:' + versions.androidx implementation 'androidx.appcompat:appcompat:' + versions.androidx
api 'androidx.lifecycle:lifecycle-extensions:' + versions.lifecycle api 'androidx.lifecycle:lifecycle-extensions:' + versions.lifecycle
api 'com.squareup.okhttp3:okhttp:' + versions.okHttp api 'com.squareup.okhttp3:okhttp:' + versions.okHttp
implementation 'com.google.dagger:dagger:' + versions.dagger
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
} }
apply from: '../spotless.gradle' apply from: '../spotless.gradle'

View file

@ -46,7 +46,14 @@ class LoadingIndicatorFrame(
isFocusable = true isFocusable = true
} }
fun setIsLoading(isLoading: Boolean) { fun observe(
owner: LifecycleOwner,
data: LiveData<Boolean>
) = data.observe(owner, Observer {
setIsLoading(it)
})
private fun setIsLoading(isLoading: Boolean) {
delayHandler.removeCallbacks(showRunnable) delayHandler.removeCallbacks(showRunnable)
if (isLoading) { if (isLoading) {
delayHandler.postDelayed(showRunnable, SHOW_DELAY_MS) delayHandler.postDelayed(showRunnable, SHOW_DELAY_MS)
@ -54,11 +61,4 @@ class LoadingIndicatorFrame(
hide() hide()
} }
} }
fun observe(
owner: LifecycleOwner,
data: LiveData<Boolean>
) = data.observe(owner, Observer {
setIsLoading(it)
})
} }