diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6d6fe70..721fc23 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,6 +5,8 @@ import java.io.FileNotFoundException plugins { id("com.android.application") id("kotlin-android") + id("kotlin-kapt") + id("dagger.hilt.android.plugin") } val keystoreProperties = Properties() @@ -64,7 +66,8 @@ android { dependencies { implementation(project(":piholeclient")) implementation(libs.bundles.coroutines) - implementation("io.insert-koin:koin-androidx-viewmodel:${libs.versions.koin.get()}") + implementation(libs.hilt.android.core) + kapt(libs.hilt.android.kapt) implementation(libs.androidx.core) implementation(libs.androidx.appcompat) implementation(libs.material) diff --git a/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt b/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt index 68eda10..3e824ae 100644 --- a/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt +++ b/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt @@ -7,17 +7,20 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.wbrawner.piholeclient.PiHoleApiService import com.wbrawner.piholeclient.VersionResponse +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.net.ConnectException import java.net.SocketTimeoutException +import javax.inject.Inject const val KEY_BASE_URL = "baseUrl" const val KEY_API_KEY = "apiKey" const val IP_MIN = 0 const val IP_MAX = 255 -class AddPiHelperViewModel( +@HiltViewModel +class AddPiHelperViewModel @Inject constructor( private val sharedPreferences: SharedPreferences, private val apiService: PiHoleApiService ) : ViewModel() { diff --git a/app/src/main/java/com/wbrawner/pihelper/AddPiHoleFragment.kt b/app/src/main/java/com/wbrawner/pihelper/AddPiHoleFragment.kt index 82c0710..cbe6f6b 100644 --- a/app/src/main/java/com/wbrawner/pihelper/AddPiHoleFragment.kt +++ b/app/src/main/java/com/wbrawner/pihelper/AddPiHoleFragment.kt @@ -10,15 +10,17 @@ import android.view.animation.LinearInterpolator import android.view.animation.RotateAnimation import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import com.wbrawner.pihelper.databinding.FragmentAddPiHoleBinding -import org.koin.android.ext.android.inject +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class AddPiHoleFragment : Fragment() { - private val viewModel: AddPiHelperViewModel by inject() + private val viewModel: AddPiHelperViewModel by activityViewModels() private var _binding: FragmentAddPiHoleBinding? = null private val binding get() = _binding!! diff --git a/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt b/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt index a1fa90b..bc4e0e1 100644 --- a/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt +++ b/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt @@ -13,13 +13,15 @@ import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import com.wbrawner.pihelper.MainActivity.Companion.ACTION_FORGET_PIHOLE import com.wbrawner.pihelper.databinding.FragmentInfoBinding -import org.koin.android.ext.android.inject +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class InfoFragment : Fragment() { - private val viewModel: AddPiHelperViewModel by inject() + private val viewModel: AddPiHelperViewModel by activityViewModels() private var _binding: FragmentInfoBinding? = null private val binding get() = _binding!! diff --git a/app/src/main/java/com/wbrawner/pihelper/MainActivity.kt b/app/src/main/java/com/wbrawner/pihelper/MainActivity.kt index 5a16e5e..2bf92d3 100644 --- a/app/src/main/java/com/wbrawner/pihelper/MainActivity.kt +++ b/app/src/main/java/com/wbrawner/pihelper/MainActivity.kt @@ -3,17 +3,20 @@ package com.wbrawner.pihelper import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.widget.Toast +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.navigation.NavController import androidx.navigation.findNavController import com.wbrawner.pihelper.MainFragment.Companion.ACTION_DISABLE import com.wbrawner.pihelper.MainFragment.Companion.ACTION_ENABLE import com.wbrawner.pihelper.MainFragment.Companion.EXTRA_DURATION import com.wbrawner.pihelper.databinding.ActivityMainBinding -import org.koin.android.ext.android.inject +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : AppCompatActivity() { - private val addPiHoleViewModel: AddPiHelperViewModel by inject() + private val addPiHoleViewModel: AddPiHelperViewModel by viewModels() private val navController: NavController by lazy { findNavController(R.id.content_main) } @@ -22,7 +25,14 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - window.setBackgroundDrawable(ColorDrawable(getColor(R.color.colorSurface))) + window.setBackgroundDrawable( + ColorDrawable( + ContextCompat.getColor( + this, + R.color.colorSurface + ) + ) + ) val args = when (intent.action) { ACTION_ENABLE -> { if (addPiHoleViewModel.apiKey == null) { diff --git a/app/src/main/java/com/wbrawner/pihelper/MainFragment.kt b/app/src/main/java/com/wbrawner/pihelper/MainFragment.kt index 5acfcff..aa0dc2b 100644 --- a/app/src/main/java/com/wbrawner/pihelper/MainFragment.kt +++ b/app/src/main/java/com/wbrawner/pihelper/MainFragment.kt @@ -16,16 +16,18 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat.getColor import androidx.core.text.set import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.wbrawner.pihelper.databinding.DialogDisableCustomTimeBinding import com.wbrawner.pihelper.databinding.FragmentMainBinding import com.wbrawner.piholeclient.Status +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject +@AndroidEntryPoint class MainFragment : Fragment() { - private val viewModel: PiHelperViewModel by inject() + private val viewModel: PiHelperViewModel by activityViewModels() private var _binding: FragmentMainBinding? = null private val binding get() = _binding!! diff --git a/app/src/main/java/com/wbrawner/pihelper/PiHelperApplication.kt b/app/src/main/java/com/wbrawner/pihelper/PiHelperApplication.kt index a346e9a..62c294e 100644 --- a/app/src/main/java/com/wbrawner/pihelper/PiHelperApplication.kt +++ b/app/src/main/java/com/wbrawner/pihelper/PiHelperApplication.kt @@ -1,23 +1,7 @@ package com.wbrawner.pihelper import android.app.Application -import android.content.Context -import com.wbrawner.piholeclient.piHoleClientModule -import org.koin.android.ext.koin.androidContext -import org.koin.android.ext.koin.androidLogger -import org.koin.core.context.startKoin +import dagger.hilt.android.HiltAndroidApp -@Suppress("unused") -class PiHelperApplication: Application() { - override fun onCreate() { - super.onCreate() - startKoin{ - androidLogger() - androidContext(this@PiHelperApplication) - modules(listOf( - piHoleClientModule, - piHelperModule - )) - } - } -} \ No newline at end of file +@HiltAndroidApp +class PiHelperApplication : Application() \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/pihelper/PiHelperModule.kt b/app/src/main/java/com/wbrawner/pihelper/PiHelperModule.kt index adf00c6..6867083 100644 --- a/app/src/main/java/com/wbrawner/pihelper/PiHelperModule.kt +++ b/app/src/main/java/com/wbrawner/pihelper/PiHelperModule.kt @@ -1,33 +1,36 @@ package com.wbrawner.pihelper +import android.content.Context +import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import com.wbrawner.piholeclient.NAME_BASE_URL -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.core.qualifier.named -import org.koin.dsl.module +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Named +import javax.inject.Singleton const val ENCRYPTED_SHARED_PREFS_FILE_NAME = "pihelper.prefs" -val piHelperModule = module { - single { +@Module +@InstallIn(SingletonComponent::class) +object PiHelperModule { + @Provides + @Singleton + fun providesSharedPreferences(@ApplicationContext context: Context): SharedPreferences = EncryptedSharedPreferences.create( ENCRYPTED_SHARED_PREFS_FILE_NAME, "pihelper", - get(), + context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) - } - viewModel { - AddPiHelperViewModel(get(), get()) - } - - viewModel { - PiHelperViewModel(get()) - } - - single(named(NAME_BASE_URL)) { - get().getString(KEY_BASE_URL, "") - } + @Provides + @Singleton + @Named(NAME_BASE_URL) + fun providesBaseUrl(sharedPreferences: SharedPreferences) = sharedPreferences + .getString(KEY_BASE_URL, "") } \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/pihelper/PiHelperViewModel.kt b/app/src/main/java/com/wbrawner/pihelper/PiHelperViewModel.kt index 0edc5b3..384b405 100644 --- a/app/src/main/java/com/wbrawner/pihelper/PiHelperViewModel.kt +++ b/app/src/main/java/com/wbrawner/pihelper/PiHelperViewModel.kt @@ -5,11 +5,14 @@ import androidx.lifecycle.ViewModel import com.wbrawner.piholeclient.PiHoleApiService import com.wbrawner.piholeclient.Status import com.wbrawner.piholeclient.StatusProvider +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive +import javax.inject.Inject import kotlin.coroutines.coroutineContext -class PiHelperViewModel( +@HiltViewModel +class PiHelperViewModel @Inject constructor( private val apiService: PiHoleApiService ) : ViewModel() { val status = MutableLiveData() diff --git a/app/src/main/java/com/wbrawner/pihelper/RetrieveApiKeyFragment.kt b/app/src/main/java/com/wbrawner/pihelper/RetrieveApiKeyFragment.kt index 41194cf..0d2a2f7 100644 --- a/app/src/main/java/com/wbrawner/pihelper/RetrieveApiKeyFragment.kt +++ b/app/src/main/java/com/wbrawner/pihelper/RetrieveApiKeyFragment.kt @@ -10,15 +10,17 @@ import android.view.animation.Animation import android.view.animation.LinearInterpolator import android.view.animation.RotateAnimation import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.transition.TransitionInflater import com.wbrawner.pihelper.databinding.FragmentRetrieveApiKeyBinding -import org.koin.android.ext.android.inject +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class RetrieveApiKeyFragment : Fragment() { - private val viewModel: AddPiHelperViewModel by inject() + private val viewModel: AddPiHelperViewModel by activityViewModels() private var _binding: FragmentRetrieveApiKeyBinding? = null private val binding get() = _binding!! @@ -50,8 +52,9 @@ class RetrieveApiKeyFragment : Fragment() { viewModel.authenticateWithPassword(binding.password.text.toString()) } catch (ignored: Exception) { Log.e("Pi-helper", "Failed to authenticate with password", ignored) - binding.password.error = "Failed to authenticate with given password. Please verify " + - "you've entered it correctly and try again." + binding.password.error = + "Failed to authenticate with given password. Please verify " + + "you've entered it correctly and try again." showProgress(false) } } diff --git a/app/src/main/java/com/wbrawner/pihelper/ScanNetworkFragment.kt b/app/src/main/java/com/wbrawner/pihelper/ScanNetworkFragment.kt index 7f88333..21e746a 100644 --- a/app/src/main/java/com/wbrawner/pihelper/ScanNetworkFragment.kt +++ b/app/src/main/java/com/wbrawner/pihelper/ScanNetworkFragment.kt @@ -15,6 +15,7 @@ import android.view.animation.LinearInterpolator import android.view.animation.RotateAnimation import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.FragmentNavigatorExtras @@ -22,15 +23,16 @@ import androidx.navigation.fragment.findNavController import androidx.transition.Transition import androidx.transition.TransitionInflater import com.wbrawner.pihelper.databinding.FragmentScanNetworkBinding +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject import java.net.Inet4Address +@AndroidEntryPoint class ScanNetworkFragment : Fragment() { - private val viewModel: AddPiHelperViewModel by inject() + private val viewModel: AddPiHelperViewModel by activityViewModels() private var _binding: FragmentScanNetworkBinding? = null private val binding get() = _binding!! diff --git a/build.gradle.kts b/build.gradle.kts index 09553d8..c93f256 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,9 +3,11 @@ buildscript { google() mavenCentral() } + val hiltVersion by extra("2.36") val kotlinVersion by extra("1.4.32") dependencies { classpath("com.android.tools.build:gradle:7.0.0-beta03") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") + classpath("com.google.dagger:hilt-android-gradle-plugin:$hiltVersion") } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60dfc05..c94defd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,13 +3,13 @@ androidx-core = "1.3.2" androidx-appcompat = "1.2.0" coroutines = "1.4.3" espresso = "3.3.0" -koin = "2.2.3" +hilt-android = "2.36" kotlin = "1.4.32" lifecycle = "2.2.0" material = "1.3.0" maxSdk = "30" moshi = "1.9.2" -minSdk = "21" +minSdk = "23" navigation = "2.3.2" okhttp = "4.2.2" versionCode = "1" @@ -18,6 +18,8 @@ versionName = "1.0" [libraries] androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +hilt-android-core = { module = "com.google.dagger:hilt-android", version.ref = "hilt-android" } +hilt-android-kapt = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt-android" } material = { module = "com.google.android.material:material", version.ref = "material" } moshi-core = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } diff --git a/piholeclient/build.gradle.kts b/piholeclient/build.gradle.kts index cba243b..b581c06 100644 --- a/piholeclient/build.gradle.kts +++ b/piholeclient/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.library") id("kotlin-android") id("kotlin-kapt") + id("dagger.hilt.android.plugin") } android { @@ -30,14 +31,15 @@ android { } dependencies { - implementation("io.insert-koin:koin-core:${libs.versions.koin.get()}") - implementation("com.squareup.okhttp3:okhttp:${libs.versions.okhttp.get()}") + api("com.squareup.okhttp3:okhttp:${libs.versions.okhttp.get()}") implementation("com.squareup.okhttp3:logging-interceptor:${libs.versions.okhttp.get()}") implementation(libs.androidx.core) implementation(libs.androidx.appcompat) implementation(libs.bundles.coroutines) + implementation(libs.hilt.android.core) + kapt(libs.hilt.android.kapt) implementation(libs.kotlin.reflect) - implementation(libs.moshi.core) + api(libs.moshi.core) kapt(libs.moshi.codegen) testImplementation(libs.junit) } diff --git a/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleClientModule.kt b/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleClientModule.kt index 832f7e3..a9a9e9a 100644 --- a/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleClientModule.kt +++ b/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleClientModule.kt @@ -2,23 +2,31 @@ package com.wbrawner.piholeclient import com.squareup.moshi.JsonReader import com.squareup.moshi.Moshi +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent import okhttp3.Cookie import okhttp3.CookieJar import okhttp3.HttpUrl import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import okio.Buffer -import org.koin.dsl.module import java.util.concurrent.TimeUnit +import javax.inject.Singleton const val NAME_BASE_URL = "baseUrl" -val piHoleClientModule = module { - single { - Moshi.Builder().build() - } +@Module +@InstallIn(SingletonComponent::class) +object PiHoleClientModule { + @Provides + @Singleton + fun providesMoshi(): Moshi = Moshi.Builder().build() - single { + @Provides + @Singleton + fun providesOkHttpClient(): OkHttpClient { val client = OkHttpClient.Builder() .connectTimeout(500, TimeUnit.MILLISECONDS) .cookieJar(object : CookieJar { @@ -53,10 +61,11 @@ val piHoleClientModule = module { } ) } - client.build() + return client.build() } - single { - OkHttpPiHoleApiService(get(), get()) - } + @Provides + @Singleton + fun providesPiHoleApiService(okHttpClient: OkHttpClient, moshi: Moshi): PiHoleApiService = + OkHttpPiHoleApiService(okHttpClient, moshi) } \ No newline at end of file