Switch from Dagger to Koin, resolves #35

This commit is contained in:
Aidan Follestad 2018-12-06 17:56:51 -08:00
parent c9750f5f66
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">
<modules>
<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$/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$/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" />
</modules>
</component>

View file

@ -18,7 +18,7 @@ android {
}
dependencies {
implementation project(':utilities')
implementation project(':common')
implementation project(':engine')
implementation project(':data')
implementation project(':notifications')
@ -32,16 +32,22 @@ dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin
implementation 'com.google.dagger:dagger:' + versions.dagger
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
implementation 'org.koin:koin-android:' + versions.koin
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.jakewharton.timber:timber:' + versions.timber
testImplementation 'junit:junit:' + versions.junit
testImplementation 'org.mockito:mockito-core:' + versions.mockito
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin
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'

View file

@ -16,55 +16,42 @@
package com.afollestad.nocknock
import android.app.Application
import com.afollestad.nocknock.di.AppComponent
import com.afollestad.nocknock.di.DaggerAppComponent
import com.afollestad.nocknock.engine.validation.BootReceiver
import com.afollestad.nocknock.engine.validation.ValidationJob
import com.afollestad.nocknock.engine.engineModule
import com.afollestad.nocknock.koin.mainModule
import com.afollestad.nocknock.koin.viewModelModule
import com.afollestad.nocknock.notifications.NockNotificationManager
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.Injector
import com.afollestad.nocknock.utilities.ext.systemService
import okhttp3.OkHttpClient
import com.afollestad.nocknock.notifications.notificationsModule
import com.afollestad.nocknock.utilities.utilitiesModule
import org.koin.android.ext.android.inject
import org.koin.android.ext.android.startKoin
import timber.log.Timber
import timber.log.Timber.DebugTree
import javax.inject.Inject
import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */
class NockNockApp : Application(), Injector {
private lateinit var appComponent: AppComponent
@Inject lateinit var nockNotificationManager: NockNotificationManager
class NockNockApp : Application() {
private var resumedActivities: Int = 0
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
val okHttpClient = OkHttpClient.Builder()
.addNetworkInterceptor { chain ->
val request = chain.request()
.newBuilder()
.addHeader("User-Agent", "com.afollestad.nocknock")
.build()
chain.proceed(request)
}
.build()
appComponent = DaggerAppComponent.builder()
.application(this)
.okHttpClient(okHttpClient)
.jobScheduler(systemService(JOB_SCHEDULER_SERVICE))
.notificationManager(systemService(NOTIFICATION_SERVICE))
.build()
appComponent.inject(this)
val modules = listOf(
mainModule,
engineModule,
utilitiesModule,
notificationsModule,
viewModelModule
)
startKoin(
androidContext = this,
modules = modules
)
val nockNotificationManager by inject<NockNotificationManager>()
onActivityLifeChange { activity, resumed ->
if (resumed) {
resumedActivities++
@ -77,13 +64,4 @@ class NockNockApp : Application(), Injector {
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
* limitations under the License.
*/
package com.afollestad.nocknock.di.viewmodels
package com.afollestad.nocknock.ui
import androidx.lifecycle.ViewModel
import com.afollestad.nocknock.di.qualifiers.MainDispatcher
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import org.jetbrains.annotations.TestOnly
import javax.inject.Inject
abstract class ScopedViewModel : ViewModel() {
@Inject
@MainDispatcher
lateinit var mainDispatcher: CoroutineDispatcher
/** @author Aidan Follestad (@afollestad) */
abstract class ScopedViewModel(mainDispatcher: CoroutineDispatcher) : ViewModel() {
private val job = Job()
protected val scope = CoroutineScope(job + mainDispatcher)

View file

@ -19,11 +19,8 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.afollestad.nocknock.R
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.conceal
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.toolbar
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.properties.Delegates.notNull
@ -58,19 +55,13 @@ class AddSiteActivity : AppCompatActivity() {
var revealCy by notNull<Int>()
var revealRadius by notNull<Float>()
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
internal var isClosing = false
private val viewModel by lazy {
return@lazy ViewModelProviders.of(this, viewModelFactory)
.get(AddSiteViewModel::class.java)
}
private val viewModel by viewModel<AddSiteViewModel>()
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injector().injectInto(this)
setContentView(R.layout.activity_addsite)
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.TERM_SEARCH
import com.afollestad.nocknock.data.putSite
import com.afollestad.nocknock.di.viewmodels.ScopedViewModel
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.map
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */
class AddSiteViewModel @Inject constructor(
class AddSiteViewModel(
private val database: AppDatabase,
private val validationManager: ValidationManager,
@field:IoDispatcher private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(), LifecycleObserver {
mainDispatcher: CoroutineDispatcher,
private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
// Public properties
val name = MutableLiveData<String>()

View file

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

View file

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

View file

@ -21,13 +21,10 @@ import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.afollestad.nocknock.R
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.data.model.Site
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.dimenFloat
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.toolbar
import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription
import javax.inject.Inject
import org.koin.androidx.viewmodel.ext.android.viewModel
/** @author Aidan Follestad (@afollestad) */
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 =
StatusUpdateIntentReceiver(application) {
viewModel.setModel(it)
@ -70,8 +63,6 @@ class ViewSiteActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injector().injectInto(this)
setContentView(R.layout.activity_viewsite)
setupUi()

View file

@ -15,7 +15,6 @@
*/
package com.afollestad.nocknock.ui.viewsite
import android.app.Application
import androidx.annotation.CheckResult
import androidx.lifecycle.LifecycleObserver
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.textRes
import com.afollestad.nocknock.data.updateSite
import com.afollestad.nocknock.di.viewmodels.ScopedViewModel
import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.ui.ScopedViewModel
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.map
import com.afollestad.nocknock.viewcomponents.ext.zip
@ -46,16 +45,16 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import java.lang.System.currentTimeMillis
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */
class ViewSiteViewModel @Inject constructor(
private val app: Application,
class ViewSiteViewModel(
private val stringProvider: StringProvider,
private val database: AppDatabase,
private val notificationManager: NockNotificationManager,
private val validationManager: ValidationManager,
@field:IoDispatcher private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(), LifecycleObserver {
mainDispatcher: CoroutineDispatcher,
private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
lateinit var site: Site
@ -134,13 +133,13 @@ class ViewSiteViewModel @Inject constructor(
@CheckResult fun onLastCheckResultText(): LiveData<String> = lastResult.map {
if (it == null) {
app.getString(R.string.none)
stringProvider.get(R.string.none)
} else {
val statusText = it.status.textRes()
if (statusText == 0) {
it.reason
} else {
app.getString(statusText)
stringProvider.get(statusText)
}
}
}
@ -151,7 +150,7 @@ class ViewSiteViewModel @Inject constructor(
val disabled = it.first
val lastResult = it.second
if (disabled) {
app.getString(R.string.auto_checks_disabled)
stringProvider.get(R.string.auto_checks_disabled)
} else {
val lastCheck = lastResult?.timestampMs ?: currentTimeMillis()
(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
import androidx.annotation.CheckResult
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
/** @author Aidan Follestad (@afollestad) */
@ -22,12 +38,11 @@ class TestLiveData<T>(data: LiveData<T>) {
fun assertValues(vararg assertValues: T) {
val assertList = assertValues.toList()
assertWithMessage("Expected: $assertList, but got: $receivedValues").that(receivedValues)
.isEqualTo(assertList)
assertThat(receivedValues).isEqualTo(assertList)
receivedValues.clear()
}
@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
import android.app.Application
@ -52,4 +67,4 @@ class StatusUpdateIntentReceiverTest {
receiver.onPause()
verify(app).unregisterReceiver(receiver.intentReceiver)
}
}
}

View file

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

View file

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

View file

@ -15,6 +15,6 @@
*/
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-android:' + versions.coroutines
implementation 'com.google.dagger:dagger:' + versions.dagger
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
implementation 'org.koin:koin-android:' + versions.koin
implementation 'org.mozilla:rhino:' + versions.rhino
testImplementation 'junit:junit:' + versions.junit

View file

@ -1,11 +1,11 @@
/*
/**
* 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
* 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,
@ -13,63 +13,62 @@
* See the License for the specific language governing permissions and
* 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.providers.BundleProvider;
import com.afollestad.nocknock.utilities.providers.IntentProvider;
import com.afollestad.nocknock.utilities.providers.JobInfoProvider;
import com.afollestad.nocknock.utilities.providers.NotificationChannelProvider;
import com.afollestad.nocknock.utilities.providers.NotificationProvider;
import com.afollestad.nocknock.utilities.providers.RealBitmapProvider;
import com.afollestad.nocknock.utilities.providers.RealBundleProvider;
import com.afollestad.nocknock.utilities.providers.RealIntentProvider;
import com.afollestad.nocknock.utilities.providers.RealJobInfoProvider;
import com.afollestad.nocknock.utilities.providers.RealNotificationChannelProvider;
import com.afollestad.nocknock.utilities.providers.RealNotificationProvider;
import com.afollestad.nocknock.utilities.providers.RealSdkProvider;
import com.afollestad.nocknock.utilities.providers.RealStringProvider;
import com.afollestad.nocknock.utilities.providers.SdkProvider;
import com.afollestad.nocknock.utilities.providers.StringProvider;
import dagger.Binds;
import dagger.Module;
import javax.inject.Singleton;
import com.afollestad.nocknock.utilities.Qualifiers.IO_DISPATCHER
import com.afollestad.nocknock.utilities.Qualifiers.MAIN_ACTIVITY_CLASS
import com.afollestad.nocknock.utilities.Qualifiers.MAIN_DISPATCHER
import com.afollestad.nocknock.utilities.providers.BitmapProvider
import com.afollestad.nocknock.utilities.providers.BundleProvider
import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.afollestad.nocknock.utilities.providers.JobInfoProvider
import com.afollestad.nocknock.utilities.providers.NotificationChannelProvider
import com.afollestad.nocknock.utilities.providers.NotificationProvider
import com.afollestad.nocknock.utilities.providers.RealBitmapProvider
import com.afollestad.nocknock.utilities.providers.RealBundleProvider
import com.afollestad.nocknock.utilities.providers.RealIntentProvider
import com.afollestad.nocknock.utilities.providers.RealJobInfoProvider
import com.afollestad.nocknock.utilities.providers.RealNotificationChannelProvider
import com.afollestad.nocknock.utilities.providers.RealNotificationProvider
import com.afollestad.nocknock.utilities.providers.RealSdkProvider
import com.afollestad.nocknock.utilities.providers.RealStringProvider
import com.afollestad.nocknock.utilities.providers.SdkProvider
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) */
@Module
public abstract class UtilitiesModule {
val utilitiesModule = module(UTILITIES_MODULE) {
@Binds
@Singleton
abstract SdkProvider provideSdkProvider(RealSdkProvider sdkProvider);
factory(name = MAIN_DISPATCHER) { Dispatchers.Main }
@Binds
@Singleton
abstract BitmapProvider provideBitmapProvider(RealBitmapProvider bitmapProvider);
factory(name = IO_DISPATCHER) { Dispatchers.IO }
@Binds
@Singleton
abstract StringProvider provideStringProvider(RealStringProvider stringProvider);
factory { RealSdkProvider() } bind SdkProvider::class
@Binds
@Singleton
abstract IntentProvider provideIntentProvider(RealIntentProvider intentProvider);
factory { RealBitmapProvider(get()) } bind BitmapProvider::class
@Binds
@Singleton
abstract NotificationChannelProvider provideChannelProvider(
RealNotificationChannelProvider channelProvider);
factory { RealStringProvider(get()) } bind StringProvider::class
@Binds
@Singleton
abstract NotificationProvider provideNotificationProvider(
RealNotificationProvider notificationProvider);
factory {
RealIntentProvider(get(), get(name = MAIN_ACTIVITY_CLASS))
} bind IntentProvider::class
@Binds
@Singleton
abstract BundleProvider provideBundleProvider(RealBundleProvider bundleProvider);
factory {
RealNotificationChannelProvider(get())
} bind NotificationChannelProvider::class
@Binds
@Singleton
abstract JobInfoProvider provideJobInfoProvider(RealJobInfoProvider jobInfoProvider);
factory { RealNotificationProvider(get()) } bind NotificationProvider::class
factory { RealBundleProvider() } bind BundleProvider::class
factory { RealJobInfoProvider(get()) } bind JobInfoProvider::class
}

View file

@ -15,11 +15,10 @@
*/
package com.afollestad.nocknock.utilities.providers
import android.app.Application
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory.decodeResource
import androidx.annotation.DrawableRes
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */
interface BitmapProvider {
@ -28,11 +27,11 @@ interface BitmapProvider {
}
/** @author Aidan Follestad (@afollestad) */
class RealBitmapProvider @Inject constructor(
private val app: Application
class RealBitmapProvider(
private val context: Context
) : BitmapProvider {
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
import android.os.PersistableBundle
import javax.inject.Inject
interface IBundle {
fun putLong(
@ -34,7 +33,7 @@ interface BundleProvider {
}
/** @author Aidan Follestad (@afollestad) */
class RealBundleProvider @Inject constructor() : BundleProvider {
class RealBundleProvider : BundleProvider {
override fun createPersistable(bundler: IBundler): PersistableBundle {
val realBundle = PersistableBundle()

View file

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

View file

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

View file

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

View file

@ -15,13 +15,12 @@
*/
package com.afollestad.nocknock.utilities.providers
import android.app.Application
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.graphics.Bitmap
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */
interface NotificationProvider {
@ -37,8 +36,8 @@ interface NotificationProvider {
}
/** @author Aidan Follestad (@afollestad) */
class RealNotificationProvider @Inject constructor(
private val app: Application
class RealNotificationProvider(
private val context: Context
) : NotificationProvider {
override fun create(
@ -49,7 +48,7 @@ class RealNotificationProvider @Inject constructor(
smallIcon: Int,
largeIcon: Bitmap
): Notification {
return NotificationCompat.Builder(app, channelId)
return NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
.setContentText(content)
.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_CODES.O
import javax.inject.Inject
/** @author Aidan Follestad (@afollestad) */
interface SdkProvider {
@ -26,7 +25,7 @@ interface SdkProvider {
}
/** @author Aidan Follestad (@afollestad) */
class RealSdkProvider @Inject constructor() : SdkProvider {
class RealSdkProvider : SdkProvider {
override fun hasOreo() = SDK_INT >= O
}

View file

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

View file

@ -17,13 +17,10 @@ android {
}
dependencies {
implementation project(':utilities')
implementation project(':common')
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
kapt 'androidx.room:room-compiler:' + versions.room

View file

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

View file

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

View file

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

View file

@ -19,19 +19,22 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_BOOT_COMPLETED
import com.afollestad.nocknock.utilities.ext.injector
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import com.afollestad.nocknock.utilities.Qualifiers.IO_DISPATCHER
import com.afollestad.nocknock.utilities.Qualifiers.MAIN_DISPATCHER
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
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
/** @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(
context: Context,
@ -42,12 +45,10 @@ class BootReceiver : BroadcastReceiver() {
}
log("Received boot event! Let's go.")
context.injector()
.injectInto(this)
val pendingResult = goAsync()
GlobalScope.launch(Main) {
async(IO) { checkStatusManager.ensureScheduledChecks() }.await()
GlobalScope.launch(mainDispatcher) {
withContext(ioDispatcher) { validationManager.ensureScheduledChecks() }
pendingResult.resultCode = 0
pendingResult.finish()
}

View file

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

View file

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

View file

@ -14,7 +14,7 @@ android {
}
dependencies {
implementation project(':utilities')
implementation project(':common')
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-android:' + versions.coroutines
implementation 'com.google.dagger:dagger:' + versions.dagger
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
implementation 'org.koin:koin-android:' + versions.koin
implementation 'com.jakewharton.timber:timber:' + versions.timber
testImplementation 'junit:junit:' + versions.junit
testImplementation 'org.mockito:mockito-core:' + versions.mockito
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.RealIntentProvider.Companion.BASE_NOTIFICATION_REQUEST_CODE
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
/** @author Aidan Follestad (@afollestad) */
@ -45,8 +43,8 @@ interface NockNotificationManager {
}
/** @author Aidan Follestad (@afollestad) */
class RealNockNotificationManager @Inject constructor(
@AppIconRes private val appIconRes: Int,
class RealNockNotificationManager(
private val appIconRes: Int,
private val stockManager: NotificationManager,
private val bitmapProvider: BitmapProvider,
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
* limitations under the License.
*/
package com.afollestad.nocknock.utilities
package com.afollestad.nocknock.notifications
/** @author Aidan Follestad (@afollestad)*/
interface Injector {
import com.afollestad.nocknock.notifications.Qualifiers.APP_ICON_RES
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 {
implementation project(':utilities')
implementation project(':common')
implementation project(':data')
implementation 'androidx.appcompat:appcompat:' + versions.androidx
api 'androidx.lifecycle:lifecycle-extensions:' + versions.lifecycle
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'

View file

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