diff options
102 files changed, 1292 insertions, 838 deletions
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index f9ad7a67..b0ef7bf4 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -6,15 +6,6 @@ <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" /> </value> </option> - <option name="PACKAGES_IMPORT_LAYOUT"> - <value> - <package name="" alias="false" withSubpackages="true" /> - <package name="java" alias="false" withSubpackages="true" /> - <package name="javax" alias="false" withSubpackages="true" /> - <package name="kotlin" alias="false" withSubpackages="true" /> - <package name="" alias="true" withSubpackages="true" /> - </value> - </option> <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index eb2873e7..1e2d92c1 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -26,5 +26,10 @@ <option name="name" value="Google" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" /> </remote-repository> + <remote-repository> + <option name="id" value="MavenRepo" /> + <option name="name" value="MavenRepo" /> + <option name="url" value="https://repo.maven.apache.org/maven2/" /> + </remote-repository> </component> </project>
\ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 357d21d4..1046d52f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' +apply plugin: 'dagger.hilt.android.plugin' //apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'com.gladed.androidgitversion' @@ -52,7 +53,7 @@ android { testInstrumentationRunner "com.pitchedapps.frost.FrostTestRunner" javaCompileOptions { annotationProcessorOptions { - arguments = ["room.schemaLocation": "$projectDir/src/schemas".toString()] + arguments += ["room.schemaLocation": "$projectDir/src/schemas".toString()] } } } @@ -270,10 +271,16 @@ dependencies { implementation "androidx.biometric:biometric:${Versions.andxBiometric}" - implementation kau.Dependencies.koin testImplementation kau.Dependencies.koinTest androidTestImplementation kau.Dependencies.koinTest + implementation kau.Dependencies.hilt + kapt kau.Dependencies.hiltCompiler + testImplementation kau.Dependencies.hiltTest + kaptTest kau.Dependencies.hiltCompiler + androidTestImplementation kau.Dependencies.hiltTest + kaptAndroidTest kau.Dependencies.hiltCompiler + implementation kau.Dependencies.coroutines implementation "org.apache.commons:commons-text:${Versions.apacheCommonsText}" @@ -313,6 +320,10 @@ dependencies { } +kapt { + correctErrorTypes true +} + def kotlinResolutions = ['kotlin-reflect', 'kotlin-stdlib', 'kotlin-stdlib-jdk7', diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/FrostTestApp.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/FrostTestApp.kt index 9d0caae6..2a834cc8 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/FrostTestApp.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/FrostTestApp.kt @@ -19,22 +19,13 @@ package com.pitchedapps.frost import android.app.Application import android.content.Context import androidx.test.runner.AndroidJUnitRunner -import ca.allanwang.kau.kpref.KPrefFactory -import ca.allanwang.kau.kpref.KPrefFactoryInMemory -import com.pitchedapps.frost.db.FrostDatabase -import com.pitchedapps.frost.facebook.FbCookie -import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs +import dagger.hilt.android.testing.HiltTestApplication import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -import org.koin.android.ext.koin.androidContext -import org.koin.android.ext.koin.androidLogger import org.koin.core.component.KoinComponent import org.koin.core.component.get -import org.koin.core.context.startKoin -import org.koin.core.module.Module -import org.koin.dsl.module class FrostTestRunner : AndroidJUnitRunner() { override fun newApplication( @@ -42,7 +33,7 @@ class FrostTestRunner : AndroidJUnitRunner() { className: String?, context: Context? ): Application { - return super.newApplication(cl, FrostTestApp::class.java.name, context) + return super.newApplication(cl, HiltTestApplication::class.java.name, context) } } @@ -58,31 +49,3 @@ class FrostTestRule : TestRule { } } } - -class FrostTestApp : Application() { - - override fun onCreate() { - super.onCreate() - startKoin { - androidLogger() - androidContext(this@FrostTestApp) - modules( - listOf( - FrostDatabase.module(), - prefFactoryModule(), - Prefs.module(), - FbCookie.module(), - ThemeProvider.module() - ) - ) - } - } - - companion object { - fun prefFactoryModule(): Module = module { - single<KPrefFactory> { - KPrefFactoryInMemory - } - } - } -} diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/TestModules.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/TestModules.kt new file mode 100644 index 00000000..176db811 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/TestModules.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.pitchedapps.frost + +import ca.allanwang.kau.kpref.KPrefFactory +import ca.allanwang.kau.kpref.KPrefFactoryInMemory +import com.pitchedapps.frost.prefs.PrefFactoryModule +import dagger.Module +import dagger.Provides +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn + +@Module +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [PrefFactoryModule::class] +) +object PrefFactoryTestModule { + @Provides + fun factory(): KPrefFactory = KPrefFactoryInMemory +} diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/activities/ImageActivityTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/activities/ImageActivityTest.kt index a23cac0b..57441a62 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/activities/ImageActivityTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/activities/ImageActivityTest.kt @@ -26,11 +26,6 @@ import com.pitchedapps.frost.utils.ARG_COOKIE import com.pitchedapps.frost.utils.ARG_IMAGE_URL import com.pitchedapps.frost.utils.ARG_TEXT import com.pitchedapps.frost.utils.isIndirectImageUrl -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue import okhttp3.internal.closeQuietly import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse @@ -46,6 +41,11 @@ import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.rules.Timeout import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) class ImageActivityTest { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt index c028e265..bae56e2f 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt @@ -20,9 +20,9 @@ import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith import kotlin.test.AfterTest import kotlin.test.BeforeTest -import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) abstract class BaseDbTest { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt index 410f92b4..417c6678 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt @@ -16,11 +16,11 @@ */ package com.pitchedapps.frost.db +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail -import kotlinx.coroutines.runBlocking class CacheDbTest : BaseDbTest() { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt index 945ac5e6..327ead86 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt @@ -16,10 +16,10 @@ */ package com.pitchedapps.frost.db +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull -import kotlinx.coroutines.runBlocking class CookieDbTest : BaseDbTest() { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieMigrationTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieMigrationTest.kt index 6ba6e0b6..8da7c663 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieMigrationTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieMigrationTest.kt @@ -21,8 +21,8 @@ import androidx.room.testing.MigrationTestHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import kotlin.test.Test import org.junit.runner.RunWith +import kotlin.test.Test @RunWith(AndroidJUnit4::class) class CookieMigrationTest { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt index 79d7e8f5..49d18b31 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt @@ -17,13 +17,13 @@ package com.pitchedapps.frost.db import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith +import org.koin.core.error.NoBeanDefFoundException +import org.koin.test.KoinTest import kotlin.reflect.KClass import kotlin.reflect.full.functions import kotlin.test.Test import kotlin.test.assertTrue -import org.junit.runner.RunWith -import org.koin.core.error.NoBeanDefFoundException -import org.koin.test.KoinTest @RunWith(AndroidJUnit4::class) class DatabaseTest : KoinTest { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt index 39b9f513..c911ddf6 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt @@ -18,9 +18,9 @@ package com.pitchedapps.frost.db import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.defaultTabs +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals -import kotlinx.coroutines.runBlocking class GenericDbTest : BaseDbTest() { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt index 60b6ee05..6fb01350 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt @@ -19,11 +19,11 @@ package com.pitchedapps.frost.db import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL import com.pitchedapps.frost.services.NOTIF_CHANNEL_MESSAGES import com.pitchedapps.frost.services.NotificationContent +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -import kotlinx.coroutines.runBlocking class NotificationDbTest : BaseDbTest() { diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/facebook/FbCookieTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/facebook/FbCookieTest.kt index a39aa6d2..71391b5a 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/facebook/FbCookieTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/facebook/FbCookieTest.kt @@ -17,8 +17,8 @@ package com.pitchedapps.frost.facebook import android.webkit.CookieManager -import kotlin.test.assertTrue import org.junit.Test +import kotlin.test.assertTrue class FbCookieTest { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 100aeecb..1e2b438e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -20,64 +20,48 @@ import android.app.Activity import android.app.Application import android.os.Bundle import android.util.Log -import ca.allanwang.kau.kpref.KPrefFactory -import ca.allanwang.kau.kpref.KPrefFactoryAndroid import ca.allanwang.kau.logging.KL import ca.allanwang.kau.utils.buildIsLollipopAndUp -import com.pitchedapps.frost.db.FrostDatabase -import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.db.CookieDao +import com.pitchedapps.frost.db.NotificationDao import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.services.scheduleNotificationsFromPrefs import com.pitchedapps.frost.services.setupNotificationChannels import com.pitchedapps.frost.utils.FrostPglAdBlock import com.pitchedapps.frost.utils.L +import dagger.hilt.android.HiltAndroidApp import java.util.Random -import org.koin.android.ext.koin.androidContext -import org.koin.android.ext.koin.androidLogger -import org.koin.core.component.KoinComponent -import org.koin.core.component.get -import org.koin.core.context.startKoin -import org.koin.core.module.Module -import org.koin.dsl.module +import javax.inject.Inject /** * Created by Allan Wang on 2017-05-28. */ -class FrostApp : Application(), KoinComponent { +@HiltAndroidApp +class FrostApp : Application() { - private lateinit var prefs: Prefs - private lateinit var themeProvider: ThemeProvider + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var cookieDao: CookieDao + + @Inject + lateinit var notifDao: NotificationDao override fun onCreate() { - startKoin { - if (BuildConfig.DEBUG) { - androidLogger() - } - androidContext(this@FrostApp) - modules( - listOf( - FrostDatabase.module(), - prefFactoryModule(), - Prefs.module(), - FbCookie.module(), - ThemeProvider.module() - ) - ) - } - if (!buildIsLollipopAndUp) { // not supported - super.onCreate() - return - } - prefs = get() - themeProvider = get() + super.onCreate() + + if (!buildIsLollipopAndUp) return // not supported + initPrefs() L.i { "Begin Frost for Facebook" } FrostPglAdBlock.init(this) - super.onCreate() - setupNotificationChannels(this, themeProvider) scheduleNotificationsFromPrefs(prefs) @@ -122,12 +106,4 @@ class FrostApp : Application(), KoinComponent { } prefs.lastLaunch = System.currentTimeMillis() } - - companion object { - fun prefFactoryModule(): Module = module { - single<KPrefFactory> { - KPrefFactoryAndroid(get()) - } - } - } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 3248eb14..9a952932 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -44,20 +44,31 @@ import com.pitchedapps.frost.utils.EXTRA_COOKIES import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.launchNewTask import com.pitchedapps.frost.utils.loadAssets -import java.util.ArrayList +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject +import java.util.ArrayList +import javax.inject.Inject /** * Created by Allan Wang on 2017-05-28. */ +@AndroidEntryPoint class StartActivity : KauBaseActivity() { - private val fbCookie: FbCookie by inject() - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() - private val cookieDao: CookieDao by inject() - private val genericDao: GenericDao by inject() + @Inject + lateinit var fbCookie: FbCookie + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var cookieDao: CookieDao + + @Inject + lateinit var genericDao: GenericDao override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -82,10 +93,12 @@ class StartActivity : KauBaseActivity() { val cookies = ArrayList(cookieDao.selectAll()) L.i { "Cookies loaded at time ${System.currentTimeMillis()}" } L._d { - "Cookies: ${cookies.joinToString( + "Cookies: ${ + cookies.joinToString( "\t", transform = CookieEntity::toSensitiveString - )}" + ) + }" } loadAssets(themeProvider) authDefer.await() @@ -93,11 +106,13 @@ class StartActivity : KauBaseActivity() { cookies.isEmpty() -> launchNewTask<LoginActivity>() // Has cookies but no selected account prefs.userId == -1L -> launchNewTask<SelectorActivity>(cookies) - else -> startActivity<MainActivity>(intentBuilder = { - putParcelableArrayListExtra(EXTRA_COOKIES, cookies) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or - Intent.FLAG_ACTIVITY_SINGLE_TOP - }) + else -> startActivity<MainActivity>( + intentBuilder = { + putParcelableArrayListExtra(EXTRA_COOKIES, cookies) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_SINGLE_TOP + } + ) } } catch (e: Exception) { L._e(e) { "Load start failed" } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt index 74d876cb..bbf8f812 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt @@ -50,16 +50,20 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L -import org.koin.android.ext.android.inject -import org.koin.core.component.inject +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Created by Allan Wang on 2017-06-26. */ +@AndroidEntryPoint class AboutActivity : AboutActivityBase(null) { - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider override fun Configs.buildConfigs() { textColor = themeProvider.textColor @@ -144,7 +148,8 @@ class AboutActivity : AboutActivityBase(null) { } } - class AboutLinks : AbstractItem<AboutLinks.ViewHolder>(), + class AboutLinks : + AbstractItem<AboutLinks.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() { override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) @@ -186,12 +191,14 @@ class AboutActivity : AboutActivityBase(null) { ) images = - (icons.map { (icon, onClick) -> c.drawable(icon) to onClick } + iicons.map { (icon, onClick) -> - icon.toDrawable( - c, - 32 - ) to onClick - }).mapIndexed { i, (icon, onClick) -> + ( + icons.map { (icon, onClick) -> c.drawable(icon) to onClick } + iicons.map { (icon, onClick) -> + icon.toDrawable( + c, + 32 + ) to onClick + } + ).mapIndexed { i, (icon, onClick) -> ImageView(c).apply { layoutParams = ViewGroup.LayoutParams(size, size) id = 109389 + i diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt index 0553086c..36e44936 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt @@ -24,18 +24,27 @@ import com.pitchedapps.frost.contracts.VideoViewHolder import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs -import com.pitchedapps.frost.utils.setFrostTheme -import org.koin.android.ext.android.inject -import org.koin.core.component.inject +import com.pitchedapps.frost.utils.ActivityThemer +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Created by Allan Wang on 2017-06-12. */ +@AndroidEntryPoint abstract class BaseActivity : KauBaseActivity() { - val fbCookie: FbCookie by inject() - val prefs: Prefs by inject() - val themeProvider: ThemeProvider by inject() + @Inject + lateinit var fbCookie: FbCookie + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var activityThemer: ActivityThemer /** * Inherited consumer to customize back press @@ -51,7 +60,7 @@ abstract class BaseActivity : KauBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (this !is WebOverlayActivityBase) setFrostTheme(themeProvider) + if (this !is WebOverlayActivityBase) activityThemer.setFrostTheme() } override fun onStop() { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt index 394969cb..19e54f68 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -122,16 +122,22 @@ import com.pitchedapps.frost.utils.frostNavigationBar import com.pitchedapps.frost.utils.launchLogin import com.pitchedapps.frost.utils.launchNewTask import com.pitchedapps.frost.utils.launchWebOverlay -import com.pitchedapps.frost.utils.setFrostColors import com.pitchedapps.frost.utils.urlEncode import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.views.FrostVideoViewer import com.pitchedapps.frost.views.FrostViewPager import com.pitchedapps.frost.widgets.NotificationWidget -import kotlin.math.abs +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.scopes.ActivityScoped import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject +import javax.inject.Inject +import kotlin.math.abs /** * Created by Allan Wang on 20/12/17. @@ -139,9 +145,13 @@ import org.koin.android.ext.android.inject * Most of the logic that is unrelated to handling fragments */ @UseExperimental(ExperimentalCoroutinesApi::class) -abstract class BaseMainActivity : BaseActivity(), MainActivityContract, +@AndroidEntryPoint +abstract class BaseMainActivity : + BaseActivity(), + MainActivityContract, FileChooserContract by FileChooserDelegate(), - VideoViewHolder, SearchViewHolder { + VideoViewHolder, + SearchViewHolder { /** * Note that tabs themselves are initialized through a coroutine during onCreate @@ -150,8 +160,12 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, override val frameWrapper: FrameLayout get() = drawerWrapperBinding.mainContainer lateinit var drawerWrapperBinding: ActivityMainDrawerWrapperBinding lateinit var contentBinding: ActivityMainContentBinding - val cookieDao: CookieDao by inject() - val genericDao: GenericDao by inject() + + @Inject + lateinit var cookieDao: CookieDao + + @Inject + lateinit var genericDao: GenericDao interface ActivityMainContentBinding { val root: View @@ -179,6 +193,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, contentBinding = when (prefs.mainActivityLayout) { MainActivityLayout.TOP_BAR -> { val binding = ActivityMainBinding.inflate(layoutInflater) + @SuppressLint("StaticFieldLeak") object : ActivityMainContentBinding { override val root: View = binding.root override val toolbar: Toolbar = binding.toolbar @@ -190,6 +205,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, } MainActivityLayout.BOTTOM_BAR -> { val binding = ActivityMainBottomTabsBinding.inflate(layoutInflater) + @SuppressLint("StaticFieldLeak") object : ActivityMainContentBinding { override val root: View = binding.root override val toolbar: Toolbar = binding.toolbar @@ -202,7 +218,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, } drawerWrapperBinding.mainContainer.addView(contentBinding.root) with(contentBinding) { - setFrostColors { + activityThemer.setFrostColors { toolbar(toolbar) themeWindow = false header(appbar) @@ -333,15 +349,17 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, shouldShow = false fab.backgroundTintList = ColorStateList.valueOf(themeProvider.headerColor.withMinAlpha(200)) fab.hide() - appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset -> - if (!hasFab) return@OnOffsetChangedListener - val percent = abs(verticalOffset.toFloat() / appBarLayout.totalScrollRange) - val shouldShow = percent < 0.2 - if (this@BaseMainActivity.shouldShow != shouldShow) { - this@BaseMainActivity.shouldShow = shouldShow - fab.showIf(shouldShow) + appbar.addOnOffsetChangedListener( + AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset -> + if (!hasFab) return@OnOffsetChangedListener + val percent = abs(verticalOffset.toFloat() / appBarLayout.totalScrollRange) + val shouldShow = percent < 0.2 + if (this@BaseMainActivity.shouldShow != shouldShow) { + this@BaseMainActivity.shouldShow = shouldShow + fab.showIf(shouldShow) + } } - }) + ) } override fun showFab(iicon: IIcon, clickEvent: () -> Unit) { @@ -843,11 +861,13 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, this@SectionsPagerAdapter.pages.forEachIndexed { index, fbItem -> tabs.addTab( tabs.newTab() - .setCustomView(BadgedIcon(this@BaseMainActivity).apply { - iicon = fbItem.icon - }.also { - it.setAllAlpha(if (index == 0) SELECTED_TAB_ALPHA else UNSELECTED_TAB_ALPHA) - }) + .setCustomView( + BadgedIcon(this@BaseMainActivity).apply { + iicon = fbItem.icon + }.also { + it.setAllAlpha(if (index == 0) SELECTED_TAB_ALPHA else UNSELECTED_TAB_ALPHA) + } + ) ) } lastPosition = 0 @@ -921,3 +941,13 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, const val UNSELECTED_TAB_ALPHA = 128f } } + +@Module +@InstallIn(ActivityComponent::class) +object MainActivityModule { + @Provides + @ActivityScoped + fun contract(@ActivityContext context: Context): MainActivityContract = + (context as? BaseMainActivity) + ?: throw IllegalArgumentException("${context::class.java.simpleName} does not implement MainActivityContract") +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt index 935b88c9..4d2af123 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/DebugActivity.kt @@ -31,19 +31,20 @@ import com.pitchedapps.frost.databinding.ActivityDebugBinding import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.injectors.JsActions import com.pitchedapps.frost.injectors.ThemeProvider +import com.pitchedapps.frost.utils.ActivityThemer import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.createFreshDir -import com.pitchedapps.frost.utils.setFrostColors +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineExceptionHandler import java.io.File +import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.CoroutineExceptionHandler -import org.koin.android.ext.android.inject -import org.koin.core.component.inject /** * Created by Allan Wang on 05/01/18. */ +@AndroidEntryPoint class DebugActivity : KauBaseActivity() { companion object { @@ -53,7 +54,11 @@ class DebugActivity : KauBaseActivity() { fun baseDir(context: Context) = File(context.externalCacheDir, "offline_debug") } - private val themeProvider: ThemeProvider by inject() + @Inject + lateinit var activityThemer: ActivityThemer + + @Inject + lateinit var themeProvider: ThemeProvider lateinit var binding: ActivityDebugBinding @@ -72,7 +77,7 @@ class DebugActivity : KauBaseActivity() { } setTitle(R.string.debug_frost) - setFrostColors { + activityThemer.setFrostColors { toolbar(toolbar) } debugWebview.loadUrl(FbItem.FEED.url) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt index 7b8ee4d3..c5b8bdaa 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -61,33 +61,40 @@ import com.pitchedapps.frost.services.LocalService import com.pitchedapps.frost.utils.ARG_COOKIE import com.pitchedapps.frost.utils.ARG_IMAGE_URL import com.pitchedapps.frost.utils.ARG_TEXT +import com.pitchedapps.frost.utils.ActivityThemer import com.pitchedapps.frost.utils.frostDownload import com.pitchedapps.frost.utils.frostSnackbar import com.pitchedapps.frost.utils.frostUriFromFile import com.pitchedapps.frost.utils.isIndirectImageUrl import com.pitchedapps.frost.utils.logFrostEvent -import com.pitchedapps.frost.utils.setFrostColors -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import kotlin.math.abs -import kotlin.math.max +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.koin.android.ext.android.inject -import org.koin.core.component.inject +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max /** * Created by Allan Wang on 2017-07-15. */ +@AndroidEntryPoint class ImageActivity : KauBaseActivity() { - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() + @Inject + lateinit var activityThemer: ActivityThemer + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider @Volatile internal var errorRef: Throwable? = null @@ -219,12 +226,12 @@ class ImageActivity : KauBaseActivity() { setState(FabStates.SHARE) } imagePhoto.setOnImageEventListener(object : - SubsamplingScaleImageView.DefaultOnImageEventListener() { - override fun onImageLoadError(e: Exception) { - loadError(e) - } - }) - setFrostColors { + SubsamplingScaleImageView.DefaultOnImageEventListener() { + override fun onImageLoadError(e: Exception) { + loadError(e) + } + }) + activityThemer.setFrostColors { themeWindow = false } dragHelper = ViewDragHelper.create(imageDrag, ViewDragCallback()).apply { @@ -394,7 +401,7 @@ internal enum class FabStates( } catch (e: Exception) { activity.errorRef = e e.logFrostEvent("Image share failed") - activity.frostSnackbar(R.string.image_share_failed) + activity.frostSnackbar(R.string.image_share_failed, activity.themeProvider) } } }; @@ -409,7 +416,8 @@ internal enum class FabStates( * */ fun update(fab: FloatingActionButton, themeProvider: ThemeProvider) { - val tint = if (backgroundTint != Int.MAX_VALUE) backgroundTint else themeProvider.accentColor + val tint = + if (backgroundTint != Int.MAX_VALUE) backgroundTint else themeProvider.accentColor val iconColor = iconColorProvider(themeProvider) if (fab.isHidden) { fab.setIcon(iicon, color = iconColor) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt index f06c3a37..817eebe1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt @@ -48,14 +48,15 @@ import com.pitchedapps.frost.intro.IntroFragmentWelcome import com.pitchedapps.frost.intro.IntroTabContextFragment import com.pitchedapps.frost.intro.IntroTabTouchFragment import com.pitchedapps.frost.prefs.Prefs +import com.pitchedapps.frost.utils.ActivityThemer import com.pitchedapps.frost.utils.cookies import com.pitchedapps.frost.utils.launchNewTask import com.pitchedapps.frost.utils.loadAssets -import com.pitchedapps.frost.utils.setFrostTheme import com.pitchedapps.frost.widgets.NotificationWidget +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject +import javax.inject.Inject /** * Created by Allan Wang on 2017-07-25. @@ -63,11 +64,21 @@ import org.koin.android.ext.android.inject * A beautiful intro activity * Phone showcases are drawn via layers */ -class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, +@AndroidEntryPoint +class IntroActivity : + KauBaseActivity(), + ViewPager.PageTransformer, ViewPager.OnPageChangeListener { - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var activityThemer: ActivityThemer + lateinit var binding: ActivityIntroBinding private var barHasNext = true @@ -114,7 +125,7 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, indicator.invalidate() } fragments.forEach { it.themeFragment() } - setFrostTheme(themeProvider, true) + activityThemer.setFrostTheme(forceTransparent = true) } /** @@ -157,7 +168,12 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, if (f != null) ValueAnimator.ofFloat(0f, 1f).apply { addUpdateListener { - f.setTint(themeProvider.textColor.blendWith(Color.WHITE, it.animatedValue as Float)) + f.setTint( + themeProvider.textColor.blendWith( + Color.WHITE, + it.animatedValue as Float + ) + ) } duration = 600 start() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt index 5cbbfafe..949f1ddd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -45,11 +45,9 @@ import com.pitchedapps.frost.utils.frostEvent import com.pitchedapps.frost.utils.frostJsoup import com.pitchedapps.frost.utils.launchNewTask import com.pitchedapps.frost.utils.logFrostEvent -import com.pitchedapps.frost.utils.setFrostColors import com.pitchedapps.frost.utils.uniqueOnly import com.pitchedapps.frost.web.LoginWebView -import java.net.UnknownHostException -import kotlin.coroutines.resume +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel @@ -58,19 +56,24 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -import org.koin.android.ext.android.inject +import java.net.UnknownHostException +import javax.inject.Inject +import kotlin.coroutines.resume /** * Created by Allan Wang on 2017-06-01. */ +@AndroidEntryPoint class LoginActivity : BaseActivity() { + @Inject + lateinit var cookieDao: CookieDao + private val toolbar: Toolbar by bindView(R.id.toolbar) private val web: LoginWebView by bindView(R.id.login_webview) private val swipeRefresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh) private val textview: AppCompatTextView by bindView(R.id.textview) private val profile: ImageView by bindView(R.id.profile) - private val cookieDao: CookieDao by inject() private lateinit var profileLoader: RequestManager private val refreshChannel = Channel<Boolean>(10) @@ -80,7 +83,7 @@ class LoginActivity : BaseActivity() { setContentView(R.layout.activity_login) setSupportActionBar(toolbar) setTitle(R.string.kau_login) - setFrostColors { + activityThemer.setFrostColors { toolbar(toolbar) } profileLoader = GlideApp.with(profile) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt index 33215c7e..a891c7c9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt @@ -29,7 +29,6 @@ import com.mikepenz.fastadapter.listeners.ClickEventHook import com.pitchedapps.frost.R import com.pitchedapps.frost.utils.cookies import com.pitchedapps.frost.utils.launchNewTask -import com.pitchedapps.frost.utils.setFrostColors import com.pitchedapps.frost.views.AccountItem import kotlinx.coroutines.launch @@ -48,8 +47,8 @@ class SelectorActivity : BaseActivity() { setContentView(R.layout.activity_selector) recycler.layoutManager = GridLayoutManager(this, 2) recycler.adapter = adapter - adapter.add(cookies().map { AccountItem(it) }) - adapter.add(AccountItem(null)) // add account + adapter.add(cookies().map { AccountItem(it, themeProvider) }) + adapter.add(AccountItem(null, themeProvider)) // add account adapter.addEventHook(object : ClickEventHook<AccountItem>() { override fun onBind(viewHolder: RecyclerView.ViewHolder): View? = (viewHolder as? AccountItem.ViewHolder)?.itemView @@ -67,7 +66,7 @@ class SelectorActivity : BaseActivity() { } } }) - setFrostColors { + activityThemer.setFrostColors { text(text) background(container) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt index 9ce16ec7..bce3aa48 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt @@ -49,6 +49,7 @@ import com.pitchedapps.frost.settings.getFeedPrefs import com.pitchedapps.frost.settings.getNotificationPrefs import com.pitchedapps.frost.settings.getSecurityPrefs import com.pitchedapps.frost.settings.sendDebug +import com.pitchedapps.frost.utils.ActivityThemer import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.REQUEST_REFRESH import com.pitchedapps.frost.utils.REQUEST_RESTART @@ -57,20 +58,31 @@ import com.pitchedapps.frost.utils.frostChangelog import com.pitchedapps.frost.utils.frostNavigationBar import com.pitchedapps.frost.utils.launchNewTask import com.pitchedapps.frost.utils.loadAssets -import com.pitchedapps.frost.utils.setFrostTheme +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject +import javax.inject.Inject /** * Created by Allan Wang on 2017-06-06. */ +@AndroidEntryPoint class SettingsActivity : KPrefActivity() { - val fbCookie: FbCookie by inject() - val notifDao: NotificationDao by inject() - val prefs: Prefs by inject() - val themeProvider: ThemeProvider by inject() + @Inject + lateinit var fbCookie: FbCookie + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var notifDao: NotificationDao + + @Inject + lateinit var activityThemer: ActivityThemer private var resultFlag = Activity.RESULT_CANCELED @@ -175,9 +187,12 @@ class SettingsActivity : KPrefActivity() { descRes = R.string.about_frost_desc iicon = GoogleMaterial.Icon.gmd_info onClick = { - startActivityForResult<AboutActivity>(9, bundleBuilder = { - withSceneTransitionAnimation(this@SettingsActivity) - }) + startActivityForResult<AboutActivity>( + 9, + bundleBuilder = { + withSceneTransitionAnimation(this@SettingsActivity) + } + ) } } @@ -218,7 +233,7 @@ class SettingsActivity : KPrefActivity() { @SuppressLint("MissingSuperCall") override fun onCreate(savedInstanceState: Bundle?) { - setFrostTheme(themeProvider, true) + activityThemer.setFrostTheme(forceTransparent = true) super.onCreate(savedInstanceState) animate = prefs.animate themeExterior(false) @@ -227,7 +242,11 @@ class SettingsActivity : KPrefActivity() { fun themeExterior(animate: Boolean = true) { if (animate) bgCanvas.fade(themeProvider.bgColor) else bgCanvas.set(themeProvider.bgColor) - if (animate) toolbarCanvas.ripple(themeProvider.headerColor, RippleCanvas.MIDDLE, RippleCanvas.END) + if (animate) toolbarCanvas.ripple( + themeProvider.headerColor, + RippleCanvas.MIDDLE, + RippleCanvas.END + ) else toolbarCanvas.set(themeProvider.headerColor) frostNavigationBar(prefs, themeProvider) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt index f2827397..adf543df 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt @@ -42,18 +42,20 @@ import com.pitchedapps.frost.db.saveTabs import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.iitems.TabIItem import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.setFrostColors -import java.util.Collections +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject +import java.util.Collections +import javax.inject.Inject /** * Created by Allan Wang on 26/11/17. */ +@AndroidEntryPoint class TabCustomizerActivity : BaseActivity() { - private val genericDao: GenericDao by inject() + @Inject + lateinit var genericDao: GenericDao private val adapter = FastItemAdapter<TabIItem>() @@ -85,7 +87,7 @@ class TabCustomizerActivity : BaseActivity() { val remaining = FbItem.values().filter { it.name[0] != '_' }.toMutableList() remaining.removeAll(tabs) tabs.addAll(remaining) - adapter.set(tabs.map(::TabIItem)) + adapter.set(tabs.map { TabIItem(it, themeProvider) }) bindSwapper(adapter, tabRecycler) @@ -107,7 +109,7 @@ class TabCustomizerActivity : BaseActivity() { fabCancel.setIcon(GoogleMaterial.Icon.gmd_close, themeProvider.iconColor) fabCancel.backgroundTintList = ColorStateList.valueOf(themeProvider.accentColor) fabCancel.setOnClickListener { finish() } - setFrostColors { + activityThemer.setFrostColors { themeWindow = true } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt index 42d84eb7..689d9a65 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt @@ -67,7 +67,6 @@ import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.BiometricUtils import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostSnackbar -import com.pitchedapps.frost.utils.setFrostColors import com.pitchedapps.frost.views.FrostContentWeb import com.pitchedapps.frost.views.FrostVideoViewer import com.pitchedapps.frost.views.FrostWebView @@ -156,9 +155,12 @@ class WebOverlayDesktopActivity : WebOverlayActivityBase(USER_AGENT_DESKTOP_CONS class WebOverlayActivity : WebOverlayActivityBase() @UseExperimental(ExperimentalCoroutinesApi::class) -abstract class WebOverlayActivityBase(private val userAgent: String = USER_AGENT) : BaseActivity(), - ActivityContract, FrostContentContainer, - VideoViewHolder, FileChooserContract by FileChooserDelegate() { +abstract class WebOverlayActivityBase(private val userAgent: String = USER_AGENT) : + BaseActivity(), + ActivityContract, + FrostContentContainer, + VideoViewHolder, + FileChooserContract by FileChooserDelegate() { override val frameWrapper: FrameLayout by bindView(R.id.frame_wrapper) val toolbar: Toolbar by bindView(R.id.overlay_toolbar) @@ -202,10 +204,11 @@ abstract class WebOverlayActivityBase(private val userAgent: String = USER_AGENT setSupportActionBar(toolbar) supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true) - toolbar.navigationIcon = GoogleMaterial.Icon.gmd_close.toDrawable(this, 16, themeProvider.iconColor) + toolbar.navigationIcon = + GoogleMaterial.Icon.gmd_close.toDrawable(this, 16, themeProvider.iconColor) toolbar.setNavigationOnClickListener { finishSlideOut() } - setFrostColors { + activityThemer.setFrostColors { toolbar(toolbar) themeWindow = false } @@ -228,7 +231,7 @@ abstract class WebOverlayActivityBase(private val userAgent: String = USER_AGENT authDefer.await() reloadBase(true) if (prefs.firstWebOverlay) { - coordinator.frostSnackbar(R.string.web_overlay_swipe_hint) { + coordinator.frostSnackbar(R.string.web_overlay_swipe_hint, themeProvider) { duration = BaseTransientBottomBar.LENGTH_INDEFINITE setAction(R.string.kau_got_it) { dismiss() } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt index f0dacdc7..68d71a91 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt @@ -36,12 +36,14 @@ import kotlinx.android.parcel.Parcelize @Entity( tableName = "frost_cache", primaryKeys = ["id", "type"], - foreignKeys = [ForeignKey( - entity = CookieEntity::class, - parentColumns = ["cookie_id"], - childColumns = ["id"], - onDelete = ForeignKey.CASCADE - )] + foreignKeys = [ + ForeignKey( + entity = CookieEntity::class, + parentColumns = ["cookie_id"], + childColumns = ["id"], + onDelete = ForeignKey.CASCADE + ) + ] ) @Parcelize data class CacheEntity( diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index bd0b4ee0..ef763617 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -21,7 +21,12 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import com.pitchedapps.frost.BuildConfig -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.Singleton interface FrostPrivateDao { fun cookieDao(): CookieDao @@ -91,13 +96,31 @@ class FrostDatabase( ).frostBuild() return FrostDatabase(privateDb, publicDb) } - - fun module() = module { - single { create(get()) } - single { get<FrostDatabase>().cookieDao() } - single { get<FrostDatabase>().cacheDao() } - single { get<FrostDatabase>().notifDao() } - single { get<FrostDatabase>().genericDao() } - } } } + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + @Provides + @Singleton + fun frostDatabase(@ApplicationContext context: Context): FrostDatabase = + FrostDatabase.create(context) + + @Provides + @Singleton + fun cookieDao(frostDatabase: FrostDatabase): CookieDao = frostDatabase.cookieDao() + + @Provides + @Singleton + fun cacheDao(frostDatabase: FrostDatabase): CacheDao = frostDatabase.cacheDao() + + @Provides + @Singleton + fun notifDao(frostDatabase: FrostDatabase): NotificationDao = frostDatabase.notifDao() + + @Provides + @Singleton + fun genericDao(frostDatabase: FrostDatabase): GenericDao = frostDatabase.genericDao() +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 93df01dc..f8f16e26 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -32,12 +32,14 @@ import com.pitchedapps.frost.utils.L @Entity( tableName = "notifications", primaryKeys = ["notif_id", "userId"], - foreignKeys = [ForeignKey( - entity = CookieEntity::class, - parentColumns = ["cookie_id"], - childColumns = ["userId"], - onDelete = ForeignKey.CASCADE - )], + foreignKeys = [ + ForeignKey( + entity = CookieEntity::class, + parentColumns = ["cookie_id"], + childColumns = ["userId"], + onDelete = ForeignKey.CASCADE + ) + ], indices = [Index("notif_id"), Index("userId")] ) data class NotificationEntity( diff --git a/app/src/main/kotlin/com/pitchedapps/frost/debugger/OfflineWebsite.kt b/app/src/main/kotlin/com/pitchedapps/frost/debugger/OfflineWebsite.kt index 18917bce..75a13295 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/debugger/OfflineWebsite.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/debugger/OfflineWebsite.kt @@ -26,12 +26,6 @@ import com.pitchedapps.frost.utils.createFreshDir import com.pitchedapps.frost.utils.createFreshFile import com.pitchedapps.frost.utils.frostJsoup import com.pitchedapps.frost.utils.unescapeHtml -import java.io.File -import java.io.FileOutputStream -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext @@ -43,6 +37,12 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.nodes.Entities +import java.io.File +import java.io.FileOutputStream +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream /** * Created by Allan Wang on 04/01/18. diff --git a/app/src/main/kotlin/com/pitchedapps/frost/enums/MainActivityLayout.kt b/app/src/main/kotlin/com/pitchedapps/frost/enums/MainActivityLayout.kt index ec438df1..4a274b9b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/enums/MainActivityLayout.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/enums/MainActivityLayout.kt @@ -28,13 +28,17 @@ enum class MainActivityLayout( val iconColor: (ThemeProvider) -> Int ) { - TOP_BAR(R.string.top_bar, + TOP_BAR( + R.string.top_bar, { it.headerColor }, - { it.iconColor }), + { it.iconColor } + ), - BOTTOM_BAR(R.string.bottom_bar, + BOTTOM_BAR( + R.string.bottom_bar, { it.bgColor }, - { it.textColor }); + { it.textColor } + ); companion object { val values = values() // save one instance diff --git a/app/src/main/kotlin/com/pitchedapps/frost/enums/Theme.kt b/app/src/main/kotlin/com/pitchedapps/frost/enums/Theme.kt index 1c9d6aa5..fc6f6f9d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/enums/Theme.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/enums/Theme.kt @@ -39,53 +39,65 @@ enum class Theme( val iconColorGetter: (ThemePrefs) -> Int ) { - DEFAULT(R.string.kau_default, + DEFAULT( + R.string.kau_default, "default", { 0xde000000.toInt() }, { FACEBOOK_BLUE }, { 0xfffafafa.toInt() }, { FACEBOOK_BLUE }, - { Color.WHITE }), + { Color.WHITE } + ), - LIGHT(R.string.kau_light, + LIGHT( + R.string.kau_light, "material_light", { 0xde000000.toInt() }, { FACEBOOK_BLUE }, { 0xfffafafa.toInt() }, { FACEBOOK_BLUE }, - { Color.WHITE }), + { Color.WHITE } + ), - DARK(R.string.kau_dark, + DARK( + R.string.kau_dark, "material_dark", { Color.WHITE }, { BLUE_LIGHT }, { 0xff303030.toInt() }, { 0xff2e4b86.toInt() }, - { Color.WHITE }), + { Color.WHITE } + ), - AMOLED(R.string.kau_amoled, + AMOLED( + R.string.kau_amoled, "material_amoled", { Color.WHITE }, { BLUE_LIGHT }, { Color.BLACK }, { Color.BLACK }, - { Color.WHITE }), + { Color.WHITE } + ), - GLASS(R.string.kau_glass, + GLASS( + R.string.kau_glass, "material_glass", { Color.WHITE }, { BLUE_LIGHT }, { 0x80000000.toInt() }, { 0xb3000000.toInt() }, - { Color.WHITE }), + { Color.WHITE } + ), - CUSTOM(R.string.kau_custom, + CUSTOM( + R.string.kau_custom, "custom", { it.customTextColor }, { it.customAccentColor }, { it.customBackgroundColor }, { it.customHeaderColor }, - { it.customIconColor }); + { it.customIconColor } + ); @VisibleForTesting internal val file = file?.let { "$it.css" } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt index 4e932d09..ea1b0946 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -28,29 +28,28 @@ import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.cookies import com.pitchedapps.frost.utils.launchLogin -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext -import org.koin.dsl.module +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine /** * Created by Allan Wang on 2017-05-30. * * The following component manages all cookie transfers. */ -class FbCookie(private val prefs: Prefs, private val cookieDao: CookieDao) { +class FbCookie @Inject internal constructor( + private val prefs: Prefs, + private val cookieDao: CookieDao +) { companion object { private const val FB_COOKIE_DOMAIN = HTTPS_FACEBOOK_COM private const val MESSENGER_COOKIE_DOMAIN = HTTPS_MESSENGER_COM - - fun module() = module { - single { FbCookie(get(), get()) } - } } /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt index 68c629a9..0c9a7e92 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt @@ -90,31 +90,33 @@ private class SearchParserImpl : FrostParserBase<FrostSearches>(false) { ?: doc.getElementById("root") ?: return null - return FrostSearches(container.select("table[role=presentation]").mapNotNull { el -> - // Our assumption is that search entries start with an image, followed by general info - // There may be other <td />s, but we will not be parsing them - // Furthermore, the <td /> entry wraps a link, containing all the necessary info - val a = el.select("td") - .getOrNull(1) - ?.selectFirst("a") - ?: return@mapNotNull null - val url = - a.attr("href").takeIf { it.isNotEmpty() } - ?.formattedFbUrl?.formattedSearchUrl + return FrostSearches( + container.select("table[role=presentation]").mapNotNull { el -> + // Our assumption is that search entries start with an image, followed by general info + // There may be other <td />s, but we will not be parsing them + // Furthermore, the <td /> entry wraps a link, containing all the necessary info + val a = el.select("td") + .getOrNull(1) + ?.selectFirst("a") ?: return@mapNotNull null - // Currently, children should all be <div /> elements, where the first entry is the name/title - // And the other entries are additional info. - // There are also cases of nested tables, eg for the "join" button in groups. - // Those elements have <span /> texts, so we will filter by div to ignore those - val texts = a.children().filter { it.tagName() == "div" && it.hasText() } - val title = texts.firstOrNull()?.text() ?: return@mapNotNull null - val info = texts.takeIf { it.size > 1 }?.last()?.text() - L.e { a } - create( - href = url, - title = title, - description = info - ).also { L.e { it } } - }) + val url = + a.attr("href").takeIf { it.isNotEmpty() } + ?.formattedFbUrl?.formattedSearchUrl + ?: return@mapNotNull null + // Currently, children should all be <div /> elements, where the first entry is the name/title + // And the other entries are additional info. + // There are also cases of nested tables, eg for the "join" button in groups. + // Those elements have <span /> texts, so we will filter by div to ignore those + val texts = a.children().filter { it.tagName() == "div" && it.hasText() } + val title = texts.firstOrNull()?.text() ?: return@mapNotNull null + val info = texts.takeIf { it.size > 1 }?.last()?.text() + L.e { a } + create( + href = url, + title = title, + description = info + ).also { L.e { it } } + } + ) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt index c07884bc..79495b2a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt @@ -41,7 +41,7 @@ import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.REQUEST_REFRESH import com.pitchedapps.frost.utils.REQUEST_TEXT_ZOOM import com.pitchedapps.frost.utils.frostEvent -import kotlin.coroutines.CoroutineContext +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -49,8 +49,8 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject -import org.koin.core.component.inject +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext /** * Created by Allan Wang on 2017-11-07. @@ -59,7 +59,11 @@ import org.koin.core.component.inject * Must be attached to activities implementing [MainActivityContract] */ @UseExperimental(ExperimentalCoroutinesApi::class) -abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, +@AndroidEntryPoint +abstract class BaseFragment : + Fragment(), + CoroutineScope, + FragmentContract, DynamicUiContract { companion object { @@ -79,32 +83,41 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, ARG_URL to d.url, ARG_POSITION to position ) - d.put(fragment.arguments!!) + d.put(fragment.requireArguments()) return fragment } } - protected val fbCookie: FbCookie by inject() - protected val prefs: Prefs by inject() - protected val themeProvider: ThemeProvider by inject() + @Inject + protected lateinit var mainContract: MainActivityContract + + @Inject + protected lateinit var fbCookie: FbCookie + + @Inject + protected lateinit var prefs: Prefs + + @Inject + protected lateinit var themeProvider: ThemeProvider + open lateinit var job: Job override val coroutineContext: CoroutineContext get() = ContextHelper.dispatcher + job - override val baseUrl: String by lazy { arguments!!.getString(ARG_URL)!! } + override val baseUrl: String by lazy { requireArguments().getString(ARG_URL)!! } override val baseEnum: FbItem by lazy { FbItem[arguments]!! } - override val position: Int by lazy { arguments!!.getInt(ARG_POSITION) } + override val position: Int by lazy { requireArguments().getInt(ARG_POSITION) } override var valid: Boolean - get() = arguments!!.getBoolean(ARG_VALID, true) + get() = requireArguments().getBoolean(ARG_VALID, true) set(value) { if (!isActive || value || this is WebFragment) return - arguments!!.putBoolean(ARG_VALID, value) + requireArguments().putBoolean(ARG_VALID, value) frostEvent( "Native Fallback", "Item" to baseEnum.name ) - (context as MainActivityContract).reloadFragment(this) + mainContract.reloadFragment(this) } override var firstLoad: Boolean = true @@ -119,8 +132,6 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, super.onCreate(savedInstanceState) job = SupervisorJob() firstLoad = true - if (context !is MainActivityContract) - throw IllegalArgumentException("${this::class.java.simpleName} is not attached to a context implementing MainActivityContract") } final override fun onCreateView( @@ -142,9 +153,7 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, onCreateRunnable = null firstLoadRequest() detachMainObservable() - (context as? MainActivityContract)?.let { - activityReceiver = attachMainObservable(it) - } + activityReceiver = attachMainObservable(mainContract) } override fun setUserVisibleHint(isVisibleToUser: Boolean) { @@ -165,7 +174,7 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, } override fun setTitle(title: String) { - (context as? MainActivityContract)?.setTitle(title) + mainContract.setTitle(title) } override fun attachMainObservable(contract: MainActivityContract): ReceiveChannel<Int> { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt index b282b36c..28962230 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt @@ -37,9 +37,9 @@ class NotificationFragment : FrostParserFragment<FrostNotifs, NotificationIItem> override fun getDoc(cookie: String?) = frostJsoup(cookie, "${FbItem.NOTIFICATIONS.url}?more") override fun toItems(response: ParseResponse<FrostNotifs>): List<NotificationIItem> = - response.data.notifs.map { NotificationIItem(it, response.cookie) } + response.data.notifs.map { NotificationIItem(it, response.cookie, themeProvider) } override fun bindImpl(recyclerView: FrostRecyclerView) { - NotificationIItem.bindEvents(adapter, fbCookie, prefs) + NotificationIItem.bindEvents(adapter, fbCookie, prefs, themeProvider) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt index 7d9694fe..5600d49d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt @@ -28,10 +28,8 @@ import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.request.RequestOptions import com.pitchedapps.frost.facebook.FbCookie import okhttp3.Interceptor -import okhttp3.OkHttpClient import okhttp3.Response -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject /** * Created by Allan Wang on 28/12/17. @@ -62,12 +60,12 @@ class FrostGlideModule : AppGlideModule() { } } -private fun getFrostHttpClient(): OkHttpClient = - OkHttpClient.Builder().addInterceptor(FrostCookieInterceptor()).build() +// private fun getFrostHttpClient(): OkHttpClient = +// OkHttpClient.Builder().addInterceptor(FrostCookieInterceptor()).build() -class FrostCookieInterceptor : Interceptor, KoinComponent { - - private val fbCookie: FbCookie by inject() +class FrostCookieInterceptor @Inject internal constructor( + private val fbCookie: FbCookie +) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val origRequest = chain.request() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/iitems/GenericIItems.kt b/app/src/main/kotlin/com/pitchedapps/frost/iitems/GenericIItems.kt index 0c53a2b1..27263789 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/iitems/GenericIItems.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/iitems/GenericIItems.kt @@ -31,8 +31,6 @@ import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.launchWebOverlay -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject /** * Created by Allan Wang on 30/12/17. @@ -74,13 +72,18 @@ interface ClickableIItemContract { */ open class HeaderIItem( val text: String?, - itemId: Int = R.layout.iitem_header -) : KauIItem<HeaderIItem.ViewHolder>(R.layout.iitem_header, ::ViewHolder, itemId) { - - class ViewHolder(itemView: View) : FastAdapter.ViewHolder<HeaderIItem>(itemView), - KoinComponent { - - private val themeProvider: ThemeProvider by inject() + itemId: Int = R.layout.iitem_header, + private val themeProvider: ThemeProvider +) : KauIItem<HeaderIItem.ViewHolder>( + R.layout.iitem_header, + { ViewHolder(it, themeProvider) }, + itemId +) { + + class ViewHolder( + itemView: View, + private val themeProvider: ThemeProvider + ) : FastAdapter.ViewHolder<HeaderIItem>(itemView) { val text: TextView by bindView(R.id.item_header_text) @@ -103,20 +106,23 @@ open class HeaderIItem( open class TextIItem( val text: String?, override val url: String?, - itemId: Int = R.layout.iitem_text -) : KauIItem<TextIItem.ViewHolder>(R.layout.iitem_text, ::ViewHolder, itemId), + itemId: Int = R.layout.iitem_text, + private val themeProvider: ThemeProvider +) : KauIItem<TextIItem.ViewHolder>(R.layout.iitem_text, { ViewHolder(it, themeProvider) }, itemId), ClickableIItemContract { - class ViewHolder(itemView: View) : FastAdapter.ViewHolder<TextIItem>(itemView), KoinComponent { - - private val themeProvider: ThemeProvider by inject() + class ViewHolder( + itemView: View, + private val themeProvider: ThemeProvider + ) : FastAdapter.ViewHolder<TextIItem>(itemView) { val text: TextView by bindView(R.id.item_text_view) override fun bindView(item: TextIItem, payloads: List<Any>) { text.setTextColor(themeProvider.textColor) text.text = item.text - text.background = createSimpleRippleDrawable(themeProvider.bgColor, themeProvider.nativeBgColor) + text.background = + createSimpleRippleDrawable(themeProvider.bgColor, themeProvider.nativeBgColor) } override fun unbindView(item: TextIItem) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt index 8e0d5bec..f0fb1a28 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt @@ -40,19 +40,25 @@ import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.isIndependent import com.pitchedapps.frost.utils.launchWebOverlay -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject /** * Created by Allan Wang on 27/12/17. */ -class NotificationIItem(val notification: FrostNotif, val cookie: String) : - KauIItem<NotificationIItem.ViewHolder>( - R.layout.iitem_notification, ::ViewHolder - ) { +class NotificationIItem( + val notification: FrostNotif, + val cookie: String, + private val themeProvider: ThemeProvider +) : KauIItem<NotificationIItem.ViewHolder>( + R.layout.iitem_notification, { ViewHolder(it, themeProvider) } +) { companion object { - fun bindEvents(adapter: ItemAdapter<NotificationIItem>, fbCookie: FbCookie, prefs: Prefs) { + fun bindEvents( + adapter: ItemAdapter<NotificationIItem>, + fbCookie: FbCookie, + prefs: Prefs, + themeProvider: ThemeProvider + ) { adapter.fastAdapter?.apply { selectExtension { isSelectable = false @@ -62,7 +68,11 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : if (notif.unread) { adapter.set( position, - NotificationIItem(notif.copy(unread = false), item.cookie) + NotificationIItem( + notif.copy(unread = false), + item.cookie, + themeProvider + ) ) } // TODO temp fix. If url is dependent, we cannot load it directly @@ -101,10 +111,10 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : } } - class ViewHolder(itemView: View) : FastAdapter.ViewHolder<NotificationIItem>(itemView), - KoinComponent { - - private val themeProvider: ThemeProvider by inject() + class ViewHolder( + itemView: View, + private val themeProvider: ThemeProvider + ) : FastAdapter.ViewHolder<NotificationIItem>(itemView) { private val frame: ViewGroup by bindView(R.id.item_frame) private val avatar: ImageView by bindView(R.id.item_avatar) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt index 0fd39d5c..f9f9064b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/iitems/TabIItem.kt @@ -30,22 +30,23 @@ import com.mikepenz.fastadapter.drag.IDraggable import com.pitchedapps.frost.R import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.injectors.ThemeProvider -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject /** * Created by Allan Wang on 26/11/17. */ -class TabIItem(val item: FbItem) : KauIItem<TabIItem.ViewHolder>( - R.layout.iitem_tab_preview, - { ViewHolder(it) } -), IDraggable { +class TabIItem(val item: FbItem, private val themeProvider: ThemeProvider) : + KauIItem<TabIItem.ViewHolder>( + R.layout.iitem_tab_preview, + { ViewHolder(it, themeProvider) } + ), + IDraggable { override val isDraggable: Boolean = true - class ViewHolder(itemView: View) : FastAdapter.ViewHolder<TabIItem>(itemView), KoinComponent { - - private val themeProvider: ThemeProvider by inject() + class ViewHolder( + itemView: View, + private val themeProvider: ThemeProvider + ) : FastAdapter.ViewHolder<TabIItem>(itemView) { val image: ImageView by bindView(R.id.image) val text: TextView by bindView(R.id.text) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt index 55eb198e..bceb9b3e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt @@ -22,11 +22,11 @@ import androidx.annotation.VisibleForTesting import ca.allanwang.kau.kotlin.lazyContext import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.BufferedReader import java.io.FileNotFoundException import java.util.Locale -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext /** * Created by Allan Wang on 2017-05-31. diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt index 2959974f..7c52fac7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt @@ -21,8 +21,8 @@ import androidx.annotation.VisibleForTesting import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.web.FrostWebViewClient -import kotlin.random.Random import org.apache.commons.text.StringEscapeUtils +import kotlin.random.Random class JsBuilder { private val css = StringBuilder() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/ThemeProvider.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/ThemeProvider.kt index 570f3719..069c5d90 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/ThemeProvider.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/ThemeProvider.kt @@ -30,58 +30,99 @@ import com.pitchedapps.frost.enums.Theme import com.pitchedapps.frost.enums.ThemeCategory import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L -import java.io.BufferedReader -import java.io.FileNotFoundException +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.koin.core.context.GlobalContext +import java.io.BufferedReader +import java.io.FileNotFoundException +import javax.inject.Inject +import javax.inject.Singleton + +interface ThemeProvider { + val textColor: Int + + val accentColor: Int + + val accentColorForWhite: Int + + val nativeBgColor: Int + + fun nativeBgColor(unread: Boolean): Int + + val bgColor: Int + + val headerColor: Int + + val iconColor: Int + + val isCustomTheme: Boolean + + /** + * Note that while this can be loaded from any thread, it is typically done through [preload]] + */ + fun injector(category: ThemeCategory): InjectorContract + + fun setTheme(id: Int) + + fun reset() + + suspend fun preload() +} /** * Provides [InjectorContract] for each [ThemeCategory]. * Can be reloaded to take in changes from [Prefs] */ -class ThemeProvider(private val context: Context, private val prefs: Prefs) { +class ThemeProviderImpl @Inject internal constructor( + @ApplicationContext private val context: Context, + private val prefs: Prefs +) : ThemeProvider { private var theme: Theme = Theme.values[prefs.theme] + set(value) { + field = value + prefs.theme = value.ordinal + } private val injectors: MutableMap<ThemeCategory, InjectorContract> = mutableMapOf() - val textColor: Int + override val textColor: Int get() = theme.textColorGetter(prefs) - val accentColor: Int + override val accentColor: Int get() = theme.accentColorGetter(prefs) - val accentColorForWhite: Int + override val accentColorForWhite: Int get() = when { accentColor.isColorVisibleOn(Color.WHITE) -> accentColor textColor.isColorVisibleOn(Color.WHITE) -> textColor else -> FACEBOOK_BLUE } - val nativeBgColor: Int + override val nativeBgColor: Int get() = bgColor.withAlpha(30) - fun nativeBgColor(unread: Boolean) = bgColor + override fun nativeBgColor(unread: Boolean) = bgColor .colorToForeground(if (unread) 0.7f else 0.0f) .withAlpha(30) - val bgColor: Int + override val bgColor: Int get() = theme.backgroundColorGetter(prefs) - val headerColor: Int + override val headerColor: Int get() = theme.headerColorGetter(prefs) - val iconColor: Int + override val iconColor: Int get() = theme.iconColorGetter(prefs) - val isCustomTheme: Boolean + override val isCustomTheme: Boolean get() = theme == Theme.CUSTOM - /** - * Note that while this can be loaded from any thread, it is typically done through [preload]] - */ - fun injector(category: ThemeCategory): InjectorContract = + override fun injector(category: ThemeCategory): InjectorContract = injectors.getOrPut(category) { createInjector(category) } /** @@ -123,28 +164,28 @@ class ThemeProvider(private val context: Context, private val prefs: Prefs) { } } - fun setTheme(id: Int) { + override fun setTheme(id: Int) { + if (theme.ordinal == id) return theme = Theme.values[id] reset() } - fun reset() { + override fun reset() { injectors.clear() } - suspend fun preload() { + override suspend fun preload() { withContext(Dispatchers.IO) { reset() ThemeCategory.values().forEach { injector(it) } } } +} - companion object { - - fun get(): ThemeProvider = GlobalContext.get().get() - - fun module() = org.koin.dsl.module { - single { ThemeProvider(get(), get()) } - } - } +@Module +@InstallIn(SingletonComponent::class) +interface ThemeProviderModule { + @Binds + @Singleton + fun themeProvider(to: ThemeProviderImpl): ThemeProvider } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt index 486b09a9..662c44e5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroFragmentTheme.kt @@ -64,7 +64,7 @@ class IntroFragmentTheme : BaseIntroFragment(R.layout.intro_theme) { private fun View.setThemeClick(theme: Theme) { setOnClickListener { v -> - prefs.theme = theme.ordinal + themeProvider.setTheme(theme.ordinal) (activity as IntroActivity).apply { binding.ripple.ripple(themeProvider.bgColor, v.x + v.pivotX, v.y + v.pivotY) theme() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt index 8003cb2e..040fa96f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/intro/IntroMainFragments.kt @@ -35,9 +35,9 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.IntroActivity import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import kotlin.math.abs -import org.koin.android.ext.android.inject -import org.koin.core.component.inject /** * Created by Allan Wang on 2017-07-28. @@ -48,10 +48,14 @@ import org.koin.core.component.inject /** * The core intro fragment for all other fragments */ +@AndroidEntryPoint abstract class BaseIntroFragment(val layoutRes: Int) : Fragment() { - protected val prefs: Prefs by inject() - protected val themeProvider: ThemeProvider by inject() + @Inject + protected lateinit var prefs: Prefs + + @Inject + protected lateinit var themeProvider: ThemeProvider val screenWidth get() = resources.displayMetrics.widthPixels diff --git a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/CoroutineUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/CoroutineUtils.kt index b657fa1a..6f8a60a9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/CoroutineUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/CoroutineUtils.kt @@ -16,12 +16,12 @@ */ package com.pitchedapps.frost.kotlin -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext @UseExperimental(ExperimentalCoroutinesApi::class) fun <T> BroadcastChannel<T>.subscribeDuringJob( diff --git a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt index 854dcf28..74765b58 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/kotlin/Flyweight.kt @@ -17,7 +17,6 @@ package com.pitchedapps.frost.kotlin import com.pitchedapps.frost.utils.L -import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineExceptionHandler @@ -29,6 +28,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.selects.select +import java.util.concurrent.ConcurrentHashMap /** * Flyweight to keep track of values so long as they are valid. diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/OldPrefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/OldPrefs.kt index 1abed8fb..cfd8edbd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/OldPrefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/OldPrefs.kt @@ -20,6 +20,7 @@ import ca.allanwang.kau.kpref.KPref import ca.allanwang.kau.kpref.KPrefFactory import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.enums.FeedSort +import javax.inject.Inject /** * Created by Allan Wang on 2017-05-28. @@ -29,7 +30,8 @@ import com.pitchedapps.frost.enums.FeedSort * As of 2020-07-18, prefs have been split up into multiple folders */ @Deprecated(level = DeprecationLevel.WARNING, message = "Use pref segments") -class OldPrefs(factory: KPrefFactory) : KPref("${BuildConfig.APPLICATION_ID}.prefs", factory) { +class OldPrefs @Inject internal constructor(factory: KPrefFactory) : + KPref("${BuildConfig.APPLICATION_ID}.prefs", factory) { var lastLaunch: Long by kpref("last_launch", -1L) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/Prefs.kt index d31be432..0cf97c56 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/Prefs.kt @@ -16,6 +16,9 @@ */ package com.pitchedapps.frost.prefs +import android.content.Context +import ca.allanwang.kau.kpref.KPrefFactory +import ca.allanwang.kau.kpref.KPrefFactoryAndroid import com.pitchedapps.frost.prefs.sections.BehaviourPrefs import com.pitchedapps.frost.prefs.sections.BehaviourPrefsImpl import com.pitchedapps.frost.prefs.sections.CorePrefs @@ -28,8 +31,14 @@ import com.pitchedapps.frost.prefs.sections.ShowcasePrefs import com.pitchedapps.frost.prefs.sections.ShowcasePrefsImpl import com.pitchedapps.frost.prefs.sections.ThemePrefs import com.pitchedapps.frost.prefs.sections.ThemePrefsImpl -import org.koin.core.context.GlobalContext -import org.koin.dsl.module +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Inject +import javax.inject.Singleton /** * [Prefs] is no longer an actual pref, but we will expose the reset function as it is used elsewhere @@ -46,34 +55,9 @@ interface Prefs : NotifPrefs, ThemePrefs, ShowcasePrefs, - PrefsBase { - companion object { - fun get(): Prefs = GlobalContext.get().get() + PrefsBase - fun module() = module { - single<BehaviourPrefs> { BehaviourPrefsImpl(factory = get()) } - single<CorePrefs> { CorePrefsImpl(factory = get()) } - single<FeedPrefs> { FeedPrefsImpl(factory = get()) } - single<NotifPrefs> { NotifPrefsImpl(factory = get()) } - single<ThemePrefs> { ThemePrefsImpl(factory = get()) } - single<ShowcasePrefs> { ShowcasePrefsImpl(factory = get()) } - single<Prefs> { - PrefsImpl( - behaviourPrefs = get(), - corePrefs = get(), - feedPrefs = get(), - notifPrefs = get(), - themePrefs = get(), - showcasePrefs = get() - ) - } - // Needed for migration - single<OldPrefs> { OldPrefs(factory = get()) } - } - } -} - -class PrefsImpl( +class PrefsImpl @Inject internal constructor( private val behaviourPrefs: BehaviourPrefs, private val corePrefs: CorePrefs, private val feedPrefs: FeedPrefs, @@ -106,3 +90,43 @@ class PrefsImpl( showcasePrefs.deleteKeys() } } + +@Module +@InstallIn(SingletonComponent::class) +interface PrefModule { + @Binds + @Singleton + fun behaviour(to: BehaviourPrefsImpl): BehaviourPrefs + + @Binds + @Singleton + fun core(to: CorePrefsImpl): CorePrefs + + @Binds + @Singleton + fun feed(to: FeedPrefsImpl): FeedPrefs + + @Binds + @Singleton + fun notif(to: NotifPrefsImpl): NotifPrefs + + @Binds + @Singleton + fun theme(to: ThemePrefsImpl): ThemePrefs + + @Binds + @Singleton + fun showcase(to: ShowcasePrefsImpl): ShowcasePrefs + + @Binds + @Singleton + fun prefs(to: PrefsImpl): Prefs +} + +@Module +@InstallIn(SingletonComponent::class) +object PrefFactoryModule { + @Provides + @Singleton + fun factory(@ApplicationContext context: Context): KPrefFactory = KPrefFactoryAndroid(context) +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/BehaviourPrefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/BehaviourPrefs.kt index 9d621048..8842d988 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/BehaviourPrefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/BehaviourPrefs.kt @@ -21,8 +21,7 @@ import ca.allanwang.kau.kpref.KPrefFactory import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.prefs.OldPrefs import com.pitchedapps.frost.prefs.PrefsBase -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject interface BehaviourPrefs : PrefsBase { var biometricsEnabled: Boolean @@ -50,12 +49,10 @@ interface BehaviourPrefs : PrefsBase { var autoExpandTextBox: Boolean } -class BehaviourPrefsImpl( - factory: KPrefFactory -) : KPref("${BuildConfig.APPLICATION_ID}.prefs.behaviour", factory), - BehaviourPrefs, KoinComponent { - - private val oldPrefs: OldPrefs by inject() +class BehaviourPrefsImpl @Inject internal constructor( + factory: KPrefFactory, + oldPrefs: OldPrefs, +) : KPref("${BuildConfig.APPLICATION_ID}.prefs.behaviour", factory), BehaviourPrefs { override var biometricsEnabled: Boolean by kpref( "biometrics_enabled", diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/CorePrefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/CorePrefs.kt index 6d3885cb..880a7225 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/CorePrefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/CorePrefs.kt @@ -21,8 +21,7 @@ import ca.allanwang.kau.kpref.KPrefFactory import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.prefs.OldPrefs import com.pitchedapps.frost.prefs.PrefsBase -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject interface CorePrefs : PrefsBase { var lastLaunch: Long @@ -56,12 +55,10 @@ interface CorePrefs : PrefsBase { var messageScrollToBottom: Boolean } -class CorePrefsImpl( - factory: KPrefFactory -) : KPref("${BuildConfig.APPLICATION_ID}.prefs.core", factory), - CorePrefs, KoinComponent { - - private val oldPrefs: OldPrefs by inject() +class CorePrefsImpl @Inject internal constructor( + factory: KPrefFactory, + oldPrefs: OldPrefs, +) : KPref("${BuildConfig.APPLICATION_ID}.prefs.core", factory), CorePrefs { override var lastLaunch: Long by kpref("last_launch", oldPrefs.lastLaunch /* -1L */) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/FeedPrefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/FeedPrefs.kt index 3fe2cfd8..00df9743 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/FeedPrefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/FeedPrefs.kt @@ -22,8 +22,7 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.enums.MainActivityLayout import com.pitchedapps.frost.prefs.OldPrefs import com.pitchedapps.frost.prefs.PrefsBase -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject interface FeedPrefs : PrefsBase { var webTextScaling: Int @@ -51,12 +50,10 @@ interface FeedPrefs : PrefsBase { var showPostReactions: Boolean } -class FeedPrefsImpl( - factory: KPrefFactory -) : KPref("${BuildConfig.APPLICATION_ID}.prefs.feed", factory), - FeedPrefs, KoinComponent { - - private val oldPrefs: OldPrefs by inject() +class FeedPrefsImpl @Inject internal constructor( + factory: KPrefFactory, + oldPrefs: OldPrefs +) : KPref("${BuildConfig.APPLICATION_ID}.prefs.feed", factory), FeedPrefs { override var webTextScaling: Int by kpref("web_text_scaling", oldPrefs.webTextScaling /* 100 */) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/NotifPrefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/NotifPrefs.kt index a9a6956f..5e34c105 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/NotifPrefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/NotifPrefs.kt @@ -21,8 +21,7 @@ import ca.allanwang.kau.kpref.KPrefFactory import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.prefs.OldPrefs import com.pitchedapps.frost.prefs.PrefsBase -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject interface NotifPrefs : PrefsBase { var notificationKeywords: Set<String> @@ -48,12 +47,10 @@ interface NotifPrefs : PrefsBase { var notificationFreq: Long } -class NotifPrefsImpl( - factory: KPrefFactory -) : KPref("${BuildConfig.APPLICATION_ID}.prefs.notif", factory), - NotifPrefs, KoinComponent { - - private val oldPrefs: OldPrefs by inject() +class NotifPrefsImpl @Inject internal constructor( + factory: KPrefFactory, + oldPrefs: OldPrefs, +) : KPref("${BuildConfig.APPLICATION_ID}.prefs.notif", factory), NotifPrefs { override var notificationKeywords: Set<String> by kpref( "notification_keywords", diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ShowcasePrefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ShowcasePrefs.kt index 516a14c5..dce8b898 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ShowcasePrefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ShowcasePrefs.kt @@ -20,6 +20,7 @@ import ca.allanwang.kau.kpref.KPref import ca.allanwang.kau.kpref.KPrefFactory import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.prefs.PrefsBase +import javax.inject.Inject interface ShowcasePrefs : PrefsBase { /** @@ -35,10 +36,9 @@ interface ShowcasePrefs : PrefsBase { * * Showcase prefs that offer one time helpers to guide new users */ -class ShowcasePrefsImpl( +class ShowcasePrefsImpl @Inject internal constructor( factory: KPrefFactory -) : KPref("${BuildConfig.APPLICATION_ID}.showcase", factory), - ShowcasePrefs { +) : KPref("${BuildConfig.APPLICATION_ID}.showcase", factory), ShowcasePrefs { override val firstWebOverlay: Boolean by kprefSingle("first_web_overlay") diff --git a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ThemePrefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ThemePrefs.kt index 47496d6d..b024b2d3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ThemePrefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/prefs/sections/ThemePrefs.kt @@ -19,11 +19,9 @@ package com.pitchedapps.frost.prefs.sections import ca.allanwang.kau.kpref.KPref import ca.allanwang.kau.kpref.KPrefFactory import com.pitchedapps.frost.BuildConfig -import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.OldPrefs import com.pitchedapps.frost.prefs.PrefsBase -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject interface ThemePrefs : PrefsBase { var theme: Int @@ -41,17 +39,16 @@ interface ThemePrefs : PrefsBase { var tintNavBar: Boolean } -class ThemePrefsImpl( - factory: KPrefFactory -) : KPref("${BuildConfig.APPLICATION_ID}.prefs.theme", factory), - ThemePrefs, KoinComponent { +class ThemePrefsImpl @Inject internal constructor( + factory: KPrefFactory, + oldPrefs: OldPrefs, +) : KPref("${BuildConfig.APPLICATION_ID}.prefs.theme", factory), ThemePrefs { - private val oldPrefs: OldPrefs by inject() - private val themeProvider: ThemeProvider by inject() - - override var theme: Int by kpref("theme", oldPrefs.theme /* 0 */) { - themeProvider.setTheme(it) - } + /** + * Note that this is purely for the pref storage. Updating themes should use + * ThemeProvider + */ + override var theme: Int by kpref("theme", oldPrefs.theme /* 0 */) override var customTextColor: Int by kpref( "color_text", diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt index 4d1317d5..0db08d0f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt @@ -20,9 +20,9 @@ import android.app.job.JobParameters import android.app.job.JobService import androidx.annotation.CallSuper import ca.allanwang.kau.utils.ContextHelper -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlin.coroutines.CoroutineContext abstract class BaseJobService : JobService(), CoroutineScope { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt index 8219049a..3d66f1ee 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt @@ -24,10 +24,10 @@ import android.content.Context import android.os.PersistableBundle import com.pitchedapps.frost.activities.ImageActivity import com.pitchedapps.frost.utils.L -import java.io.FileFilter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.FileFilter class LocalService : BaseJobService() { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index 55dad065..01f52caa 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -29,12 +29,13 @@ import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostEvent import com.pitchedapps.frost.widgets.NotificationWidget +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.yield -import org.koin.android.ext.android.inject +import javax.inject.Inject /** * Created by Allan Wang on 2017-06-14. @@ -44,11 +45,17 @@ import org.koin.android.ext.android.inject * * All fetching is done through parsers */ +@AndroidEntryPoint class NotificationService : BaseJobService() { - private val prefs: Prefs by inject() - private val notifDao: NotificationDao by inject() - private val cookieDao: CookieDao by inject() + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var notifDao: NotificationDao + + @Inject + lateinit var cookieDao: CookieDao override fun onStopJob(params: JobParameters?): Boolean { super.onStopJob(params) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt index 439838a9..91a60d90 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt @@ -21,17 +21,19 @@ import android.content.Context import android.content.Intent import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Created by Allan Wang on 2017-05-31. * * Receiver that is triggered whenever the app updates so it can bind the notifications again */ -class UpdateReceiver : BroadcastReceiver(), KoinComponent { +@AndroidEntryPoint +class UpdateReceiver : BroadcastReceiver() { - private val prefs: Prefs by inject() + @Inject + lateinit var prefs: Prefs override fun onReceive(context: Context, intent: Intent) { if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED) return diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt index 64d9dba2..34f1ba1c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt @@ -33,7 +33,6 @@ import com.pitchedapps.frost.utils.frostEvent import com.pitchedapps.frost.utils.frostNavigationBar import com.pitchedapps.frost.utils.frostSnackbar import com.pitchedapps.frost.utils.launchTabCustomizerActivity -import com.pitchedapps.frost.utils.setFrostTheme import com.pitchedapps.frost.views.KPrefTextSeekbar /** @@ -43,7 +42,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { header(R.string.theme_customization) - text(R.string.theme, prefs::theme, { prefs.theme = it }) { + text(R.string.theme, prefs::theme, { themeProvider.setTheme(it) }) { onClick = { materialDialog { title(R.string.theme) @@ -55,7 +54,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { item.pref = index shouldRestartMain() reload() - setFrostTheme(themeProvider, true) + activityThemer.setFrostTheme(forceTransparent = true) themeExterior() invalidateOptionsMenu() frostEvent("Theme", "Count" to Theme(index).name) @@ -70,7 +69,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { fun KPrefColorPicker.KPrefColorContract.dependsOnCustom() { enabler = themeProvider::isCustomTheme - onDisabledClick = { frostSnackbar(R.string.requires_custom_theme) } + onDisabledClick = { frostSnackbar(R.string.requires_custom_theme, themeProvider) } allowCustom = true } @@ -78,53 +77,68 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { themeProvider.reset() } - colorPicker(R.string.text_color, prefs::customTextColor, { - prefs.customTextColor = it - reload() - invalidateCustomTheme() - shouldRestartMain() - }) { + colorPicker( + R.string.text_color, prefs::customTextColor, + { + prefs.customTextColor = it + reload() + invalidateCustomTheme() + shouldRestartMain() + } + ) { dependsOnCustom() allowCustomAlpha = false } - colorPicker(R.string.accent_color, prefs::customAccentColor, { - prefs.customAccentColor = it - reload() - invalidateCustomTheme() - shouldRestartMain() - }) { + colorPicker( + R.string.accent_color, prefs::customAccentColor, + { + prefs.customAccentColor = it + reload() + invalidateCustomTheme() + shouldRestartMain() + } + ) { dependsOnCustom() allowCustomAlpha = false } - colorPicker(R.string.background_color, prefs::customBackgroundColor, { - prefs.customBackgroundColor = it - bgCanvas.ripple(it, duration = 500L) - invalidateCustomTheme() - setFrostTheme(themeProvider, true) - shouldRestartMain() - }) { + colorPicker( + R.string.background_color, prefs::customBackgroundColor, + { + prefs.customBackgroundColor = it + bgCanvas.ripple(it, duration = 500L) + invalidateCustomTheme() + activityThemer.setFrostTheme(forceTransparent = true) + shouldRestartMain() + } + ) { dependsOnCustom() allowCustomAlpha = true } - colorPicker(R.string.header_color, prefs::customHeaderColor, { - prefs.customHeaderColor = it - frostNavigationBar(prefs, themeProvider) - toolbarCanvas.ripple(it, RippleCanvas.MIDDLE, RippleCanvas.END, duration = 500L) - reload() - shouldRestartMain() - }) { + colorPicker( + R.string.header_color, prefs::customHeaderColor, + { + prefs.customHeaderColor = it + frostNavigationBar(prefs, themeProvider) + toolbarCanvas.ripple(it, RippleCanvas.MIDDLE, RippleCanvas.END, duration = 500L) + reload() + shouldRestartMain() + } + ) { dependsOnCustom() allowCustomAlpha = true } - colorPicker(R.string.icon_color, prefs::customIconColor, { - prefs.customIconColor = it - invalidateOptionsMenu() - shouldRestartMain() - }) { + colorPicker( + R.string.icon_color, prefs::customIconColor, + { + prefs.customIconColor = it + invalidateOptionsMenu() + shouldRestartMain() + } + ) { dependsOnCustom() allowCustomAlpha = false } @@ -134,7 +148,8 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { text( R.string.main_activity_layout, prefs::mainActivityLayoutType, - { prefs.mainActivityLayoutType = it }) { + { prefs.mainActivityLayoutType = it } + ) { textGetter = { string(prefs.mainActivityLayout.titleRes) } onClick = { materialDialog { @@ -158,11 +173,14 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { onClick = { launchTabCustomizerActivity() } } - checkbox(R.string.tint_nav, prefs::tintNavBar, { - prefs.tintNavBar = it - frostNavigationBar(prefs, themeProvider) - setFrostResult(REQUEST_NAV) - }) { + checkbox( + R.string.tint_nav, prefs::tintNavBar, + { + prefs.tintNavBar = it + frostNavigationBar(prefs, themeProvider) + setFrostResult(REQUEST_NAV) + } + ) { descRes = R.string.tint_nav_desc } @@ -174,12 +192,16 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = { ) { prefs.webTextScaling = it setFrostResult(REQUEST_TEXT_ZOOM) - }) + } + ) ) - checkbox(R.string.enforce_black_media_bg, prefs::blackMediaBg, { - prefs.blackMediaBg = it - }) { + checkbox( + R.string.enforce_black_media_bg, prefs::blackMediaBg, + { + prefs.blackMediaBg = it + } + ) { descRes = R.string.enforce_black_media_bg_desc } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt index 551b3ff1..a9d99763 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt @@ -36,21 +36,24 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = { checkbox( R.string.overlay_swipe, prefs::overlayEnabled, - { prefs.overlayEnabled = it; shouldRefreshMain() }) { + { prefs.overlayEnabled = it; shouldRefreshMain() } + ) { descRes = R.string.overlay_swipe_desc } checkbox( R.string.overlay_full_screen_swipe, prefs::overlayFullScreenSwipe, - { prefs.overlayFullScreenSwipe = it }) { + { prefs.overlayFullScreenSwipe = it } + ) { descRes = R.string.overlay_full_screen_swipe_desc } checkbox( R.string.open_links_in_default, prefs::linksInDefaultApp, - { prefs.linksInDefaultApp = it }) { + { prefs.linksInDefaultApp = it } + ) { descRes = R.string.open_links_in_default_desc } @@ -61,14 +64,16 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = { checkbox( R.string.force_message_bottom, prefs::messageScrollToBottom, - { prefs.messageScrollToBottom = it }) { + { prefs.messageScrollToBottom = it } + ) { descRes = R.string.force_message_bottom_desc } checkbox( R.string.auto_expand_text_box, prefs::autoExpandTextBox, - { prefs.autoExpandTextBox = it; shouldRefreshMain() }) { + { prefs.autoExpandTextBox = it; shouldRefreshMain() } + ) { descRes = R.string.auto_expand_text_box_desc } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt index 9a4751b8..b2bb1d11 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt @@ -36,14 +36,15 @@ import com.pitchedapps.frost.facebook.parsers.FrostParser import com.pitchedapps.frost.facebook.parsers.MessageParser import com.pitchedapps.frost.facebook.parsers.NotifParser import com.pitchedapps.frost.facebook.parsers.SearchParser +import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostUriFromFile import com.pitchedapps.frost.utils.sendFrostEmail -import java.io.File import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch +import java.io.File /** * Created by Allan Wang on 2017-06-30. @@ -89,10 +90,10 @@ fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = { val data = parser.parse(fbCookie.webCookie) withMainContext { loading.dismiss() - createEmail(parser, data?.data) + createEmail(parser, data?.data, prefs) } } catch (e: Exception) { - createEmail(parser, "Error: ${e.message}") + createEmail(parser, "Error: ${e.message}", prefs) } } } @@ -101,8 +102,11 @@ fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = { } } -private fun Context.createEmail(parser: FrostParser<*>, content: Any?) = - sendFrostEmail("${string(R.string.debug_report)}: ${parser::class.java.simpleName}") { +private fun Context.createEmail(parser: FrostParser<*>, content: Any?, prefs: Prefs) = + sendFrostEmail( + "${string(R.string.debug_report)}: ${parser::class.java.simpleName}", + prefs = prefs + ) { addItem("Url", parser.url) addItem("Contents", "$content") } @@ -148,7 +152,7 @@ fun SettingsActivity.sendDebug(url: String, html: String?) { File(downloader.baseDir, "$ZIP_NAME.zip") ) L.i { "Sending debug zip with uri $zipUri" } - sendFrostEmail(R.string.debug_report_email_title) { + sendFrostEmail(R.string.debug_report_email_title, prefs = prefs) { addItem("Url", url) addAttachment(zipUri) extras = { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt index 30b16f81..995500f0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt @@ -36,10 +36,13 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = { // Experimental content ends here -------------------- - checkbox(R.string.verbose_logging, prefs::verboseLogging, { - prefs.verboseLogging = it - KL.shouldLog = { it != Log.VERBOSE } - }) { + checkbox( + R.string.verbose_logging, prefs::verboseLogging, + { + prefs.verboseLogging = it + KL.shouldLog = { it != Log.VERBOSE } + } + ) { descRes = R.string.verbose_logging_desc } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt index f8d10437..914874a2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt @@ -49,66 +49,93 @@ fun SettingsActivity.getFeedPrefs(): KPrefAdapterBuilder.() -> Unit = { textGetter = { string(FeedSort(it).textRes) } } - checkbox(R.string.aggressive_recents, prefs::aggressiveRecents, { - prefs.aggressiveRecents = it - shouldRefreshMain() - }) { + checkbox( + R.string.aggressive_recents, prefs::aggressiveRecents, + { + prefs.aggressiveRecents = it + shouldRefreshMain() + } + ) { descRes = R.string.aggressive_recents_desc } - checkbox(R.string.composer, prefs::showComposer, { - prefs.showComposer = it - shouldRefreshMain() - }) { + checkbox( + R.string.composer, prefs::showComposer, + { + prefs.showComposer = it + shouldRefreshMain() + } + ) { descRes = R.string.composer_desc } - checkbox(R.string.create_fab, prefs::showCreateFab, { - prefs.showCreateFab = it - setFrostResult(REQUEST_FAB) - }) { + checkbox( + R.string.create_fab, prefs::showCreateFab, + { + prefs.showCreateFab = it + setFrostResult(REQUEST_FAB) + } + ) { descRes = R.string.create_fab_desc } - checkbox(R.string.suggested_friends, prefs::showSuggestedFriends, { - prefs.showSuggestedFriends = it - shouldRefreshMain() - }) { + checkbox( + R.string.suggested_friends, prefs::showSuggestedFriends, + { + prefs.showSuggestedFriends = it + shouldRefreshMain() + } + ) { descRes = R.string.suggested_friends_desc } - checkbox(R.string.suggested_groups, prefs::showSuggestedGroups, { - prefs.showSuggestedGroups = it - shouldRefreshMain() - }) { + checkbox( + R.string.suggested_groups, prefs::showSuggestedGroups, + { + prefs.showSuggestedGroups = it + shouldRefreshMain() + } + ) { descRes = R.string.suggested_groups_desc } - checkbox(R.string.show_stories, prefs::showStories, { - prefs.showStories = it - shouldRefreshMain() - }) { + checkbox( + R.string.show_stories, prefs::showStories, + { + prefs.showStories = it + shouldRefreshMain() + } + ) { descRes = R.string.show_stories_desc } - checkbox(R.string.show_post_actions, prefs::showPostActions, { - prefs.showPostActions = it - shouldRefreshMain() - }) { + checkbox( + R.string.show_post_actions, prefs::showPostActions, + { + prefs.showPostActions = it + shouldRefreshMain() + } + ) { descRes = R.string.show_post_actions_desc } - checkbox(R.string.show_post_reactions, prefs::showPostReactions, { - prefs.showPostReactions = it - shouldRefreshMain() - }) { + checkbox( + R.string.show_post_reactions, prefs::showPostReactions, + { + prefs.showPostReactions = it + shouldRefreshMain() + } + ) { descRes = R.string.show_post_reactions_desc } - checkbox(R.string.full_size_image, prefs::fullSizeImage, { - prefs.fullSizeImage = it - shouldRefreshMain() - }) { + checkbox( + R.string.full_size_image, prefs::fullSizeImage, + { + prefs.fullSizeImage = it + shouldRefreshMain() + } + ) { descRes = R.string.full_size_image_desc } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt index c275227f..9df8bf86 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt @@ -28,7 +28,8 @@ fun SettingsActivity.getNetworkPrefs(): KPrefAdapterBuilder.() -> Unit = { checkbox( R.string.network_media_on_metered, { !prefs.loadMediaOnMeteredNetwork }, - { prefs.loadMediaOnMeteredNetwork = !it }) { + { prefs.loadMediaOnMeteredNetwork = !it } + ) { descRes = R.string.network_media_on_metered_desc } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt index 397825f6..a0fd2e3d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -54,7 +54,8 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { text( R.string.notification_frequency, prefs::notificationFreq, - { prefs.notificationFreq = it }) { + { prefs.notificationFreq = it } + ) { val options = longArrayOf(15, 30, 60, 120, 180, 300, 1440, 2880) val texts = options.map { if (it <= 0) string(R.string.no_notifications) else minuteToText(it) } @@ -87,34 +88,42 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { } } - checkbox(R.string.notification_general, prefs::notificationsGeneral, + checkbox( + R.string.notification_general, prefs::notificationsGeneral, { prefs.notificationsGeneral = it reloadByTitle(R.string.notification_general_all_accounts) if (!prefs.notificationsInstantMessages) reloadByTitle(R.string.notification_frequency) - }) { + } + ) { descRes = R.string.notification_general_desc } - checkbox(R.string.notification_general_all_accounts, prefs::notificationAllAccounts, - { prefs.notificationAllAccounts = it }) { + checkbox( + R.string.notification_general_all_accounts, prefs::notificationAllAccounts, + { prefs.notificationAllAccounts = it } + ) { descRes = R.string.notification_general_all_accounts_desc enabler = { prefs.notificationsGeneral } } - checkbox(R.string.notification_messages, prefs::notificationsInstantMessages, + checkbox( + R.string.notification_messages, prefs::notificationsInstantMessages, { prefs.notificationsInstantMessages = it reloadByTitle(R.string.notification_messages_all_accounts) if (!prefs.notificationsGeneral) reloadByTitle(R.string.notification_frequency) - }) { + } + ) { descRes = R.string.notification_messages_desc } - checkbox(R.string.notification_messages_all_accounts, prefs::notificationsImAllAccounts, - { prefs.notificationsImAllAccounts = it }) { + checkbox( + R.string.notification_messages_all_accounts, prefs::notificationsImAllAccounts, + { prefs.notificationsImAllAccounts = it } + ) { descRes = R.string.notification_messages_all_accounts_desc enabler = { prefs.notificationsInstantMessages } } @@ -129,13 +138,16 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { } } } else { - checkbox(R.string.notification_sound, prefs::notificationSound, { - prefs.notificationSound = it - reloadByTitle( - R.string.notification_ringtone, - R.string.message_ringtone - ) - }) + checkbox( + R.string.notification_sound, prefs::notificationSound, + { + prefs.notificationSound = it + reloadByTitle( + R.string.notification_ringtone, + R.string.message_ringtone + ) + } + ) fun KPrefText.KPrefTextContract<String>.ringtone(code: Int) { enabler = prefs::notificationSound @@ -162,21 +174,29 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { } } - text(R.string.notification_ringtone, prefs::notificationRingtone, - { prefs.notificationRingtone = it }) { + text( + R.string.notification_ringtone, prefs::notificationRingtone, + { prefs.notificationRingtone = it } + ) { ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE) } - text(R.string.message_ringtone, prefs::messageRingtone, - { prefs.messageRingtone = it }) { + text( + R.string.message_ringtone, prefs::messageRingtone, + { prefs.messageRingtone = it } + ) { ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE) } - checkbox(R.string.notification_vibrate, prefs::notificationVibrate, - { prefs.notificationVibrate = it }) + checkbox( + R.string.notification_vibrate, prefs::notificationVibrate, + { prefs.notificationVibrate = it } + ) - checkbox(R.string.notification_lights, prefs::notificationLights, - { prefs.notificationLights = it }) + checkbox( + R.string.notification_lights, prefs::notificationLights, + { prefs.notificationLights = it } + ) } if (BuildConfig.DEBUG) { @@ -195,7 +215,7 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { val text = if (fetchNotifications()) R.string.notification_fetch_success else R.string.notification_fetch_fail - frostSnackbar(text) + frostSnackbar(text, themeProvider) } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt index aa38fffd..61c69f10 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt @@ -31,18 +31,21 @@ fun SettingsActivity.getSecurityPrefs(): KPrefAdapterBuilder.() -> Unit = { descRes = R.string.security_disclaimer_info } - checkbox(R.string.enable_biometrics, prefs::biometricsEnabled, { - launch { + checkbox( + R.string.enable_biometrics, prefs::biometricsEnabled, + { + launch { /* * For security, we should request authentication when: * - enabling to ensure that it is supported * - disabling to ensure that it is permitted */ - BiometricUtils.authenticate(this@getSecurityPrefs, prefs, force = true).await() - prefs.biometricsEnabled = it - reloadByTitle(R.string.enable_biometrics) + BiometricUtils.authenticate(this@getSecurityPrefs, prefs, force = true).await() + prefs.biometricsEnabled = it + reloadByTitle(R.string.enable_biometrics) + } } - }) { + ) { descRes = R.string.enable_biometrics_desc enabler = { BiometricUtils.isSupported(this@getSecurityPrefs) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt index 80f83c8d..1860d6f6 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt @@ -27,10 +27,10 @@ import androidx.lifecycle.OnLifecycleEvent import ca.allanwang.kau.utils.string import com.pitchedapps.frost.R import com.pitchedapps.frost.prefs.Prefs +import kotlinx.coroutines.CompletableDeferred import java.util.concurrent.Executor import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -import kotlinx.coroutines.CompletableDeferred typealias BiometricDeferred = CompletableDeferred<BiometricPrompt.CryptoObject?> diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 13ce2920..ec8aec6c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -72,20 +72,20 @@ import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.injectors.JsAssets import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs -import java.io.File -import java.io.IOException -import java.net.URLEncoder -import java.nio.charset.StandardCharsets -import java.util.ArrayList -import java.util.Locale +import dagger.hilt.android.scopes.ActivityScoped import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import java.io.File +import java.io.IOException +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.ArrayList +import java.util.Locale +import javax.inject.Inject /** * Created by Allan Wang on 2017-06-03. @@ -101,9 +101,12 @@ inline fun <reified T : Activity> Context.launchNewTask( cookieList: ArrayList<CookieEntity> = arrayListOf(), clearStack: Boolean = false ) { - startActivity<T>(clearStack, intentBuilder = { - putParcelableArrayListExtra(EXTRA_COOKIES, cookieList) - }) + startActivity<T>( + clearStack, + intentBuilder = { + putParcelableArrayListExtra(EXTRA_COOKIES, cookieList) + } + ) } fun Context.launchLogin(cookieList: ArrayList<CookieEntity>, clearStack: Boolean = true) { @@ -133,9 +136,12 @@ private inline fun <reified T : WebOverlayActivityBase> Context.launchWebOverlay fbCookie.logout(this@launchWebOverlayImpl) } } else if (!(prefs.linksInDefaultApp && resolveActivityForUri(Uri.parse(argUrl)))) { - startActivity<T>(false, intentBuilder = { - putExtra(ARG_URL, argUrl) - }) + startActivity<T>( + false, + intentBuilder = { + putExtra(ARG_URL, argUrl) + } + ) } } @@ -155,12 +161,14 @@ private fun Context.fadeBundle() = ActivityOptions.makeCustomAnimation( ).toBundle() fun Context.launchImageActivity(imageUrl: String, text: String? = null, cookie: String? = null) { - startActivity<ImageActivity>(intentBuilder = { - putExtras(fadeBundle()) - putExtra(ARG_IMAGE_URL, imageUrl) - putExtra(ARG_TEXT, text) - putExtra(ARG_COOKIE, cookie) - }) + startActivity<ImageActivity>( + intentBuilder = { + putExtras(fadeBundle()) + putExtra(ARG_IMAGE_URL, imageUrl) + putExtra(ARG_TEXT, text) + putExtra(ARG_COOKIE, cookie) + } + ) } fun Activity.launchTabCustomizerActivity() { @@ -168,30 +176,45 @@ fun Activity.launchTabCustomizerActivity() { SettingsActivity.ACTIVITY_REQUEST_TABS, bundleBuilder = { with(fadeBundle()) - }) + } + ) } fun WebOverlayActivity.url(): String { return intent.getStringExtra(ARG_URL) ?: FbItem.FEED.url } -fun Activity.setFrostTheme(themeProvider: ThemeProvider, forceTransparent: Boolean = false) { - val isTransparent = - forceTransparent || (Color.alpha(themeProvider.bgColor) != 255) || (Color.alpha( - themeProvider.headerColor - ) != 255) - if (themeProvider.bgColor.isColorDark) { - setTheme(if (isTransparent) R.style.FrostTheme_Transparent else R.style.FrostTheme) - } else { - setTheme(if (isTransparent) R.style.FrostTheme_Light_Transparent else R.style.FrostTheme_Light) +@ActivityScoped +class ActivityThemer @Inject constructor( + private val activity: Activity, + private val prefs: Prefs, + private val themeProvider: ThemeProvider +) { + fun setFrostTheme(forceTransparent: Boolean = false) { + val isTransparent = + forceTransparent || (Color.alpha(themeProvider.bgColor) != 255) || ( + Color.alpha( + themeProvider.headerColor + ) != 255 + ) + if (themeProvider.bgColor.isColorDark) { + activity.setTheme(if (isTransparent) R.style.FrostTheme_Transparent else R.style.FrostTheme) + } else { + activity.setTheme(if (isTransparent) R.style.FrostTheme_Light_Transparent else R.style.FrostTheme_Light) + } } -} - -class ActivityThemeUtils : KoinComponent { - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() + fun setFrostColors(builder: ActivityThemeUtils.() -> Unit) { + val themer = ActivityThemeUtils(prefs = prefs, themeProvider = themeProvider) + themer.builder() + themer.theme(activity) + } +} +class ActivityThemeUtils( + private val prefs: Prefs, + private val themeProvider: ThemeProvider +) { private var toolbar: Toolbar? = null var themeWindow = true private var texts = mutableListOf<TextView>() @@ -229,12 +252,6 @@ class ActivityThemeUtils : KoinComponent { } } -inline fun Activity.setFrostColors(builder: ActivityThemeUtils.() -> Unit) { - val themer = ActivityThemeUtils() - themer.builder() - themer.theme(this) -} - fun frostEvent(name: String, vararg events: Pair<String, Any>) { // todo bind L.v { "Event: $name ${events.joinToString(", ")}" } @@ -249,15 +266,23 @@ fun Throwable?.logFrostEvent(text: String) { frostEvent("Errors", "text" to text, "message" to (this?.message ?: "NA")) } -fun Activity.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) = - snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder)) +fun Activity.frostSnackbar( + @StringRes text: Int, + themeProvider: ThemeProvider, + builder: Snackbar.() -> Unit = {} +) = snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(themeProvider, builder)) -fun View.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) = - snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(builder)) +fun View.frostSnackbar( + @StringRes text: Int, + themeProvider: ThemeProvider, + builder: Snackbar.() -> Unit = {} +) = snackbar(text, Snackbar.LENGTH_LONG, frostSnackbar(themeProvider, builder)) @SuppressLint("RestrictedApi") -private inline fun frostSnackbar(crossinline builder: Snackbar.() -> Unit): Snackbar.() -> Unit = { - val themeProvider = ThemeProvider.get() +private inline fun frostSnackbar( + themeProvider: ThemeProvider, + crossinline builder: Snackbar.() -> Unit +): Snackbar.() -> Unit = { builder() // hacky workaround, but it has proper checks and shouldn't crash ((view as? FrameLayout)?.getChildAt(0) as? SnackbarContentLayout)?.apply { @@ -404,18 +429,20 @@ fun Context.frostUri(entry: String): Uri { inline fun Context.sendFrostEmail( @StringRes subjectId: Int, + prefs: Prefs, crossinline builder: EmailBuilder.() -> Unit -) = - sendFrostEmail(string(subjectId), builder) +) = sendFrostEmail(string(subjectId), prefs, builder) -inline fun Context.sendFrostEmail(subjectId: String, crossinline builder: EmailBuilder.() -> Unit) = - sendEmail("", subjectId) { - builder() - addFrostDetails() - } +inline fun Context.sendFrostEmail( + subjectId: String, + prefs: Prefs, + crossinline builder: EmailBuilder.() -> Unit +) = sendEmail("", subjectId) { + builder() + addFrostDetails(prefs) +} -fun EmailBuilder.addFrostDetails() { - val prefs = Prefs.get() +fun EmailBuilder.addFrostDetails(prefs: Prefs) { addItem("Prev version", prefs.prevVersionCode.toString()) val proTag = "FO" addItem("Random Frost ID", "${prefs.frostId}-$proTag") diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt index 03dbb9fb..f3c81578 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt @@ -32,7 +32,7 @@ import com.pitchedapps.frost.prefs.Prefs /** * Created by Allan Wang on 2017-07-07. */ -fun Context.showWebContextMenu(wc: WebContext, fbCookie: FbCookie) { +fun Context.showWebContextMenu(wc: WebContext, fbCookie: FbCookie, prefs: Prefs) { if (wc.isEmpty) return var title = wc.url ?: string(R.string.menu) title = @@ -45,7 +45,7 @@ fun Context.showWebContextMenu(wc: WebContext, fbCookie: FbCookie) { materialDialog { title(text = title) listItems(items = menuItems.map { string(it.textId) }) { _, position, _ -> - menuItems[position].onClick(this@showWebContextMenu, wc, fbCookie) + menuItems[position].onClick(this@showWebContextMenu, wc, fbCookie, prefs) } onDismiss { // showing the dialog interrupts the touch down event, so we must ensure that the viewpager's swipe is enabled @@ -67,15 +67,16 @@ class WebContext(val unformattedUrl: String?, val text: String?) { enum class WebContextType( val textId: Int, val constraint: (wc: WebContext) -> Boolean, - val onClick: (c: Context, wc: WebContext, fc: FbCookie) -> Unit + val onClick: (c: Context, wc: WebContext, fc: FbCookie, prefs: Prefs) -> Unit ) { OPEN_LINK( R.string.open_link, { it.hasUrl }, - { c, wc, fc -> c.launchWebOverlay(wc.url!!, fc, Prefs.get()) }), - COPY_LINK(R.string.copy_link, { it.hasUrl }, { c, wc, _ -> c.copyToClipboard(wc.url) }), - COPY_TEXT(R.string.copy_text, { it.hasText }, { c, wc, _ -> c.copyToClipboard(wc.text) }), - SHARE_LINK(R.string.share_link, { it.hasUrl }, { c, wc, _ -> c.shareText(wc.url) }) + { c, wc, fc, prefs -> c.launchWebOverlay(wc.url!!, fc, prefs) } + ), + COPY_LINK(R.string.copy_link, { it.hasUrl }, { c, wc, _, _ -> c.copyToClipboard(wc.url) }), + COPY_TEXT(R.string.copy_text, { it.hasText }, { c, wc, _, _ -> c.copyToClipboard(wc.text) }), + SHARE_LINK(R.string.share_link, { it.hasUrl }, { c, wc, _, _ -> c.shareText(wc.url) }) ; companion object { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt index 6eebd36f..e15538b8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt @@ -38,17 +38,14 @@ import com.pitchedapps.frost.facebook.profilePictureUrl import com.pitchedapps.frost.glide.FrostGlide import com.pitchedapps.frost.glide.GlideApp import com.pitchedapps.frost.injectors.ThemeProvider -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject /** * Created by Allan Wang on 2017-06-05. */ -class AccountItem(val cookie: CookieEntity?) : - KauIItem<AccountItem.ViewHolder>(R.layout.view_account, { ViewHolder(it) }, R.id.item_account), - KoinComponent { - - private val themeProvider: ThemeProvider by inject() +class AccountItem( + val cookie: CookieEntity?, + private val themeProvider: ThemeProvider +) : KauIItem<AccountItem.ViewHolder>(R.layout.view_account, { ViewHolder(it) }, R.id.item_account) { override fun bindView(holder: ViewHolder, payloads: List<Any>) { super.bindView(holder, payloads) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt index 78af4edf..61be271f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/BadgedIcon.kt @@ -31,20 +31,25 @@ import com.mikepenz.iconics.typeface.IIcon import com.pitchedapps.frost.databinding.ViewBadgedIconBinding import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Created by Allan Wang on 2017-06-19. */ +@AndroidEntryPoint class BadgedIcon @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr), KoinComponent { +) : ConstraintLayout(context, attrs, defStyleAttr) { + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() private val binding: ViewBadgedIconBinding = ViewBadgedIconBinding.inflate(LayoutInflater.from(context), this, true) @@ -54,7 +59,8 @@ class BadgedIcon @JvmOverloads constructor( private fun ViewBadgedIconBinding.init() { val badgeColor = - prefs.mainActivityLayout.backgroundColor(themeProvider).withAlpha(255).colorToForeground(0.2f) + prefs.mainActivityLayout.backgroundColor(themeProvider).withAlpha(255) + .colorToForeground(0.2f) val badgeBackground = GradientDrawable( GradientDrawable.Orientation.BOTTOM_TOP, diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt index 177b8862..124a75df 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt @@ -42,13 +42,13 @@ import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.kotlin.subscribeDuringJob import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.channels.ReceiveChannel -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject class FrostContentWeb @JvmOverloads constructor( context: Context, @@ -76,17 +76,48 @@ abstract class FrostContentView<out T> @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), - FrostContentParent, KoinComponent where T : View, T : FrostContentCore { +) : FrostContentViewBase(context, attrs, defStyleAttr, defStyleRes), + FrostContentParent where T : View, T : FrostContentCore { - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() - private val refresh: SwipeRefreshLayout by bindView(R.id.content_refresh) - private val progress: ProgressBar by bindView(R.id.content_progress) val coreView: T by bindView(R.id.content_core) override val core: FrostContentCore get() = coreView +} + +/** + * Subsection of [FrostContentView] that is [AndroidEntryPoint] friendly (no generics) + */ +@UseExperimental(ExperimentalCoroutinesApi::class) +@AndroidEntryPoint +abstract class FrostContentViewBase( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), + FrostContentParent { + + // No JvmOverloads due to hilt + constructor(context: Context) : this(context, null) + + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this( + context, + attrs, + defStyleAttr, + 0 + ) + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + private val refresh: SwipeRefreshLayout by bindView(R.id.content_refresh) + private val progress: ProgressBar by bindView(R.id.content_progress) /** * While this can be conflated, there exist situations where we wish to watch refresh cycles. @@ -129,7 +160,7 @@ abstract class FrostContentView<out T> @JvmOverloads constructor( */ protected fun init() { inflate(context, layoutRes, this) - coreView.parent = this + core.parent = this reloadThemeSelf() } @@ -140,9 +171,7 @@ abstract class FrostContentView<out T> @JvmOverloads constructor( scope = container core.bind(container) refresh.setOnRefreshListener { - with(coreView) { - reload(true) - } + core.reload(true) } refreshChannel.subscribeDuringJob(scope, ContextHelper.coroutineContext) { r -> @@ -160,11 +189,11 @@ abstract class FrostContentView<out T> @JvmOverloads constructor( override fun reloadTheme() { reloadThemeSelf() - coreView.reloadTheme() + core.reloadTheme() } override fun reloadTextSize() { - coreView.reloadTextSize() + core.reloadTextSize() } override fun reloadThemeSelf() { @@ -195,7 +224,7 @@ abstract class FrostContentView<out T> @JvmOverloads constructor( return false // still in progress; do not bother with load } L.v { "Registered transition" } - with(coreView) { + with(core) { refreshReceiver = refreshChannel.openSubscription().also { receiver -> scope.launchMain { var loading = false diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt index 89009b56..2ab00916 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt @@ -28,25 +28,25 @@ import com.pitchedapps.frost.contracts.FrostContentCore import com.pitchedapps.frost.contracts.FrostContentParent import com.pitchedapps.frost.fragments.RecyclerContentContract import com.pitchedapps.frost.prefs.Prefs +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject /** * Created by Allan Wang on 2017-05-29. * */ @UseExperimental(ExperimentalCoroutinesApi::class) +@AndroidEntryPoint class FrostRecyclerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : RecyclerView(context, attrs, defStyleAttr), - KoinComponent, - FrostContentCore { +) : RecyclerView(context, attrs, defStyleAttr), FrostContentCore { - private val prefs: Prefs by inject() + @Inject + lateinit var prefs: Prefs override fun reload(animate: Boolean) = reloadBase(animate) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt index a76aeea0..836d8666 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt @@ -48,17 +48,18 @@ import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostDownload -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Created by Allan Wang on 2017-10-13. */ +@AndroidEntryPoint class FrostVideoViewer @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr), FrostVideoViewerContract, KoinComponent { +) : FrameLayout(context, attrs, defStyleAttr), FrostVideoViewerContract { companion object { /** @@ -88,9 +89,14 @@ class FrostVideoViewer @JvmOverloads constructor( } } - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() - private val cookieDao: CookieDao by inject() + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var cookieDao: CookieDao private val binding: ViewVideoBinding = ViewVideoBinding.inflate(LayoutInflater.from(context), this, true) @@ -192,12 +198,12 @@ class FrostVideoViewer @JvmOverloads constructor( fun updateLocation() { with(binding) { viewTreeObserver.addOnGlobalLayoutListener(object : - ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - video.updateLocation() - viewTreeObserver.removeOnGlobalLayoutListener(this) - } - }) + ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + video.updateLocation() + viewTreeObserver.removeOnGlobalLayoutListener(this) + } + }) } } @@ -206,7 +212,8 @@ class FrostVideoViewer @JvmOverloads constructor( if (video.isExpanded) videoToolbar.fadeIn( duration = CONTROL_ANIMATION_DURATION, - onStart = { videoToolbar.visible() }) + onStart = { videoToolbar.visible() } + ) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt index e3a23e12..f04a2f57 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostViewPager.kt @@ -22,18 +22,23 @@ import android.util.AttributeSet import android.view.MotionEvent import androidx.viewpager.widget.ViewPager import com.pitchedapps.frost.prefs.Prefs -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Created by Allan Wang on 2017-07-07. * * Basic override to allow us to control swiping */ -class FrostViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - ViewPager(context, attrs), KoinComponent { +@AndroidEntryPoint +class FrostViewPager @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : ViewPager(context, attrs) { + + @Inject + lateinit var prefs: Prefs - private val prefs: Prefs by inject() var enableSwipe = true override fun onInterceptTouchEvent(ev: MotionEvent?) = diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt index ecd8c093..ec012ed5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt @@ -43,28 +43,34 @@ import com.pitchedapps.frost.web.FrostChromeClient import com.pitchedapps.frost.web.FrostJSI import com.pitchedapps.frost.web.FrostWebViewClient import com.pitchedapps.frost.web.NestedWebView +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import kotlin.math.abs import kotlin.math.max import kotlin.math.min -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject /** * Created by Allan Wang on 2017-05-29. * */ +@AndroidEntryPoint class FrostWebView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : NestedWebView(context, attrs, defStyleAttr), - FrostContentCore, - KoinComponent { +) : NestedWebView(context, attrs, defStyleAttr), FrostContentCore { - val fbCookie: FbCookie by inject() - val prefs: Prefs by inject() - val themeProvider: ThemeProvider by inject() - val cookieDao: CookieDao by inject() + @Inject + lateinit var fbCookie: FbCookie + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var cookieDao: CookieDao override fun reload(animate: Boolean) { if (parent.registerTransition(false, animate)) @@ -92,7 +98,7 @@ class FrostWebView @JvmOverloads constructor( // attempt to get custom client; otherwise fallback to original frostWebClient = (container as? WebFragment)?.client(this) ?: FrostWebViewClient(this) webViewClient = frostWebClient - webChromeClient = FrostChromeClient(this) + webChromeClient = FrostChromeClient(this, themeProvider) addJavascriptInterface(FrostJSI(this), "Frost") setBackgroundColor(Color.TRANSPARENT) setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt index c3d5a90e..a0a7e5b1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/Keywords.kt @@ -39,20 +39,25 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.pitchedapps.frost.R import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * Created by Allan Wang on 2017-06-19. */ +@AndroidEntryPoint class Keywords @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr), KoinComponent { +) : ConstraintLayout(context, attrs, defStyleAttr) { + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() val editText: AppCompatEditText by bindView(R.id.edit_text) val addIcon: ImageView by bindView(R.id.add_icon) val recycler: RecyclerView by bindView(R.id.recycler) @@ -61,16 +66,21 @@ class Keywords @JvmOverloads constructor( init { inflate(context, R.layout.view_keywords, this) editText.tint(themeProvider.textColor) - addIcon.setImageDrawable(GoogleMaterial.Icon.gmd_add.keywordDrawable(context, themeProvider)) + addIcon.setImageDrawable( + GoogleMaterial.Icon.gmd_add.keywordDrawable( + context, + themeProvider + ) + ) addIcon.setOnClickListener { if (editText.text.isNullOrEmpty()) editText.error = context.string(R.string.empty_keyword) else { - adapter.add(0, KeywordItem(editText.text.toString())) + adapter.add(0, KeywordItem(editText.text.toString(), themeProvider)) editText.text?.clear() } } - adapter.add(prefs.notificationKeywords.map { KeywordItem(it) }) + adapter.add(prefs.notificationKeywords.map { KeywordItem(it, themeProvider) }) recycler.layoutManager = LinearLayoutManager(context) recycler.adapter = adapter adapter.addEventHook(object : ClickEventHook<KeywordItem>() { @@ -96,9 +106,12 @@ class Keywords @JvmOverloads constructor( private fun IIcon.keywordDrawable(context: Context, themeProvider: ThemeProvider): Drawable = toDrawable(context, 20, themeProvider.textColor) -class KeywordItem(val keyword: String) : AbstractItem<KeywordItem.ViewHolder>() { +class KeywordItem( + val keyword: String, + private val themeProvider: ThemeProvider +) : AbstractItem<KeywordItem.ViewHolder>() { - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) + override fun getViewHolder(v: View): ViewHolder = ViewHolder(v, themeProvider) override val layoutRes: Int get() = R.layout.item_keyword @@ -116,9 +129,11 @@ class KeywordItem(val keyword: String) : AbstractItem<KeywordItem.ViewHolder>() holder.text.text = null } - class ViewHolder(v: View) : RecyclerView.ViewHolder(v), KoinComponent { + class ViewHolder( + v: View, + themeProvider: ThemeProvider + ) : RecyclerView.ViewHolder(v) { - private val themeProvider: ThemeProvider by inject() val text: AppCompatTextView by bindView(R.id.keyword_text) val delete: ImageView by bindView(R.id.keyword_delete) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt index fe85ab9c..187e7d4e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt @@ -34,25 +34,30 @@ import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.createFreshFile import com.pitchedapps.frost.utils.isFacebookUrl -import java.io.File +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import java.io.File +import javax.inject.Inject /** * Created by Allan Wang on 2018-01-05. * * A barebone webview with a refresh listener */ +@AndroidEntryPoint class DebugWebView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : WebView(context, attrs, defStyleAttr), KoinComponent { +) : WebView(context, attrs, defStyleAttr) { + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() var onPageFinished: (String?) -> Unit = {} init { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt index 43b7071e..e687dd2d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -32,6 +32,7 @@ import com.afollestad.materialdialogs.callbacks.onDismiss import com.afollestad.materialdialogs.input.input import com.pitchedapps.frost.R import com.pitchedapps.frost.contracts.ActivityContract +import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostSnackbar import com.pitchedapps.frost.views.FrostWebView @@ -46,7 +47,10 @@ import kotlinx.coroutines.channels.SendChannel /** * The default chrome client */ -class FrostChromeClient(web: FrostWebView) : WebChromeClient() { +class FrostChromeClient( + web: FrostWebView, + private val themeProvider: ThemeProvider +) : WebChromeClient() { private val refresh: SendChannel<Boolean> = web.parent.refreshChannel private val progress: SendChannel<Int> = web.parent.progressChannel @@ -80,7 +84,7 @@ class FrostChromeClient(web: FrostWebView) : WebChromeClient() { fileChooserParams: FileChooserParams ): Boolean { activity?.openFileChooser(filePathCallback, fileChooserParams) - ?: webView.frostSnackbar(R.string.file_chooser_not_found) + ?: webView.frostSnackbar(R.string.file_chooser_not_found, themeProvider) return activity != null } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index 12e10e10..0d7bbb79 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -84,7 +84,8 @@ class FrostJSI(val web: FrostWebView) { web.post { context.showWebContextMenu( WebContext(url.takeIf { it.isIndependent }, text), - fbCookie + fbCookie, + prefs ) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt index f38e5ced..5586d479 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt @@ -21,8 +21,8 @@ import android.webkit.WebResourceResponse import android.webkit.WebView import com.pitchedapps.frost.utils.FrostPglAdBlock import com.pitchedapps.frost.utils.L -import java.io.ByteArrayInputStream import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import java.io.ByteArrayInputStream /** * Created by Allan Wang on 2017-07-13. diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt index fadbadf9..bcfcc109 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -42,23 +42,30 @@ import com.pitchedapps.frost.injectors.jsInject import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.isFacebookUrl +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.coroutineScope -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import javax.inject.Inject /** * Created by Allan Wang on 2017-05-29. */ +@AndroidEntryPoint class LoginWebView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : WebView(context, attrs, defStyleAttr), KoinComponent { +) : WebView(context, attrs, defStyleAttr) { + + @Inject + lateinit var fbCookie: FbCookie + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider - private val fbCookie: FbCookie by inject() - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() private val completable: CompletableDeferred<CookieEntity> = CompletableDeferred() private lateinit var progressCallback: (Int) -> Unit diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt index da0ebf0d..294c2ac1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt @@ -30,12 +30,17 @@ import androidx.core.view.ViewCompat * * Webview extension that handles nested scrolls */ -open class NestedWebView @JvmOverloads constructor( +open class NestedWebView( context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + attrs: AttributeSet?, + defStyleAttr: Int ) : WebView(context, attrs, defStyleAttr), NestedScrollingChild { + // No JvmOverloads due to hilt + constructor(context: Context) : this(context, null) + + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + private lateinit var childHelper: NestedScrollingChildHelper private var lastY: Int = 0 private val scrollOffset = IntArray(2) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index 3bf37f7d..9efe83f6 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -48,13 +48,17 @@ import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.services.NotificationContent import com.pitchedapps.frost.services.NotificationType import com.pitchedapps.frost.utils.toReadableTime -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject -class NotificationWidget : AppWidgetProvider(), KoinComponent { +@AndroidEntryPoint +class NotificationWidget : AppWidgetProvider() { - private val prefs: Prefs by inject() - private val themeProvider: ThemeProvider by inject() + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider override fun onUpdate( context: Context, @@ -142,9 +146,17 @@ private fun RemoteViews.setIcon( } } +@AndroidEntryPoint class NotificationWidgetService : RemoteViewsService() { + + @Inject + lateinit var themeProvider: ThemeProvider + + @Inject + lateinit var notifDao: NotificationDao + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = - NotificationWidgetDataProvider(this, intent) + NotificationWidgetDataProvider(this, intent, themeProvider, notifDao) companion object { fun createIntent(context: Context, type: NotificationType, userId: Long): Intent = @@ -154,13 +166,12 @@ class NotificationWidgetService : RemoteViewsService() { } } -class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : - RemoteViewsService.RemoteViewsFactory, - KoinComponent { - - private val themeProvider: ThemeProvider by inject() - - private val notifDao: NotificationDao by inject() +class NotificationWidgetDataProvider( + private val context: Context, + private val intent: Intent, + private val themeProvider: ThemeProvider, + private val notifDao: NotificationDao +) : RemoteViewsService.RemoteViewsFactory { @Volatile private var content: List<NotificationContent> = emptyList() diff --git a/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt index 4064a5ff..48587ea9 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt @@ -18,6 +18,12 @@ package com.pitchedapps.frost.debugger import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.internal.COOKIE +import kotlinx.coroutines.runBlocking +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Assume.assumeTrue import java.io.File import java.util.zip.ZipFile import kotlin.test.AfterTest @@ -27,12 +33,6 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -import kotlinx.coroutines.runBlocking -import okhttp3.mockwebserver.Dispatcher -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import okhttp3.mockwebserver.RecordedRequest -import org.junit.Assume.assumeTrue /** * Created by Allan Wang on 05/01/18. @@ -46,8 +46,9 @@ class OfflineWebsiteTest { fun before() { val buildPath = if (File("").absoluteFile.name == "app") "build/offline_test" else "app/build/offline_test" - baseDir = File(buildPath) - assertTrue(baseDir.deleteRecursively(), "Failed to clean base dir") + val rootDir = File(buildPath) + rootDir.deleteRecursively() + baseDir = rootDir.resolve(System.currentTimeMillis().toString()) server = MockWebServer() server.start() } @@ -61,7 +62,7 @@ class OfflineWebsiteTest { url: String = server.url("/").toString(), cookie: String = "" ): ZipFile { - val name = "test${System.currentTimeMillis()}" + val name = "test" runBlocking { val success = OfflineWebsite(url, cookie, baseDir = baseDir) .loadAndZip(name) @@ -247,7 +248,8 @@ class OfflineWebsiteTest { assertEquals(5, zip.size(), "2 files expected") zip.assertContentEquals( - "index.html", content + "index.html", + content .replace(css1Url.toString(), "assets/a0_1.css") .replace(css2Url.toString(), "assets/a1_2.css") .replace(js1Url.toString(), "assets/a2_1.js.txt") diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbDomTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbDomTest.kt index 544d904f..9472adfe 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbDomTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbDomTest.kt @@ -18,9 +18,9 @@ package com.pitchedapps.frost.facebook import com.pitchedapps.frost.internal.authDependent import com.pitchedapps.frost.internal.testJsoup -import kotlin.test.assertNotNull import org.junit.BeforeClass import org.junit.Test +import kotlin.test.assertNotNull class FbDomTest { diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt index 08957ea9..d37e55c1 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt @@ -16,9 +16,9 @@ */ package com.pitchedapps.frost.facebook -import kotlin.test.assertEquals import org.apache.commons.text.StringEscapeUtils import org.junit.Test +import kotlin.test.assertEquals /** * Created by Allan Wang on 24/12/17. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt index 433fdb2a..558cffa1 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt @@ -18,10 +18,10 @@ package com.pitchedapps.frost.facebook import com.pitchedapps.frost.utils.isImageUrl import com.pitchedapps.frost.utils.isIndirectImageUrl +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -import org.junit.Test /** * Created by Allan Wang on 2017-07-07. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt index e3b449c0..a5955101 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt @@ -20,12 +20,12 @@ import com.pitchedapps.frost.internal.COOKIE import com.pitchedapps.frost.internal.assertComponentsNotEmpty import com.pitchedapps.frost.internal.assertDescending import com.pitchedapps.frost.internal.authDependent +import org.junit.BeforeClass +import org.junit.Test import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail -import org.junit.BeforeClass -import org.junit.Test /** * Created by Allan Wang on 24/12/17. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbFullImageTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbFullImageTest.kt index 64a9fe31..cb8dd5e1 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbFullImageTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/requests/FbFullImageTest.kt @@ -18,10 +18,10 @@ package com.pitchedapps.frost.facebook.requests import com.pitchedapps.frost.internal.COOKIE import com.pitchedapps.frost.internal.authDependent -import kotlin.test.assertNotNull import kotlinx.coroutines.runBlocking import org.junit.BeforeClass import org.junit.Test +import kotlin.test.assertNotNull /** * Created by Allan Wang on 12/04/18. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt index 17ce847c..7c12ba73 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt @@ -20,6 +20,7 @@ import com.pitchedapps.frost.facebook.FB_USER_MATCHER import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.get import com.pitchedapps.frost.utils.frostJsoup +import org.junit.Assume import java.io.File import java.io.FileInputStream import java.util.Properties @@ -27,7 +28,6 @@ import kotlin.reflect.full.starProjectedType import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail -import org.junit.Assume /** * Created by Allan Wang on 21/12/17. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt index 20cdd5ec..89289322 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/kotlin/FlyweightTest.kt @@ -16,6 +16,11 @@ */ package com.pitchedapps.frost.kotlin +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.runBlocking +import org.junit.Rule +import org.junit.rules.Timeout import java.util.concurrent.atomic.AtomicInteger import kotlin.test.BeforeTest import kotlin.test.Test @@ -23,11 +28,6 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.test.fail -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.runBlocking -import org.junit.Rule -import org.junit.rules.Timeout class FlyweightTest { diff --git a/app/src/test/kotlin/com/pitchedapps/frost/prefs/PrefsTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/prefs/PrefsTest.kt index 28c0c899..a51dc460 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/prefs/PrefsTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/prefs/PrefsTest.kt @@ -16,9 +16,9 @@ */ package com.pitchedapps.frost.prefs -import kotlin.test.assertEquals import org.junit.Before import org.junit.Test +import kotlin.test.assertEquals /** * Created by Allan Wang on 2017-05-31. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/BuildUtilsTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/BuildUtilsTest.kt index 596693e7..a40372af 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/utils/BuildUtilsTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/BuildUtilsTest.kt @@ -17,10 +17,10 @@ package com.pitchedapps.frost.utils import com.pitchedapps.frost.BuildConfig +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -import org.junit.Test class BuildUtilsTest { diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt index cb427faa..2744d0d8 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt @@ -17,13 +17,6 @@ package com.pitchedapps.frost.utils import com.pitchedapps.frost.kotlin.Flyweight -import java.util.concurrent.Executors -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -33,12 +26,26 @@ import kotlinx.coroutines.async import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.count import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.count +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import java.util.concurrent.Executors +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue /** * Collection of tests around coroutines @@ -67,6 +74,7 @@ class CoroutineTest { ): List<T> = withContext(Dispatchers.IO) { val data = mutableListOf<T>() + channel.receiveAsFlow() for (c in channel) { data.add(c) if (shouldEnd(c)) break @@ -118,6 +126,9 @@ class CoroutineTest { } } + private fun <T : Any> SharedFlow<T?>.takeUntilNull(): Flow<T> = + takeWhile { it != null }.filterNotNull() + /** * Sanity check to ensure that contexts are being honoured */ @@ -128,13 +139,10 @@ class CoroutineTest { Thread(r, mainTag) }.asCoroutineDispatcher() - val channel = BroadcastChannel<String>(100) - + val flow = MutableSharedFlow<String?>(100) runBlocking(Dispatchers.IO) { - val receiver1 = channel.openSubscription() - val receiver2 = channel.openSubscription() launch(mainDispatcher) { - for (thread in receiver1) { + flow.takeUntilNull().collect { thread -> assertTrue( Thread.currentThread().name.startsWith(mainTag), "Channel should be received in main thread" @@ -146,10 +154,11 @@ class CoroutineTest { } } listOf(EmptyCoroutineContext, Dispatchers.IO, Dispatchers.Default, Dispatchers.IO).map { - async(it) { channel.send(Thread.currentThread().name) } + async(it) { flow.emit(Thread.currentThread().name) } }.joinAll() - channel.close() - assertEquals(4, receiver2.count(), "Not all events received") + flow.emit(null) + val count = flow.takeUntilNull().count() + assertEquals(4, count, "Not all events received") } } @@ -159,6 +168,7 @@ class CoroutineTest { * Events should be consumed when there is no pending consumer on previous elements. */ @Test + @Ignore("Move to flow") fun throttledChannel() { val channel = Channel<Int>(Channel.CONFLATED) runBlocking { @@ -177,7 +187,7 @@ class CoroutineTest { val received = deferred.await() assertTrue( received.size < 20, - "Received data should be throttled; expected that around 1/10th of all events are consumed" + "Received data should be throttled; expected that around 1/10th of all events are consumed, but received ${received.size}" ) println(received) } diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/JsoupCleanerTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/JsoupCleanerTest.kt index 7c277dc7..7ec01e6d 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/utils/JsoupCleanerTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/JsoupCleanerTest.kt @@ -16,8 +16,8 @@ */ package com.pitchedapps.frost.utils -import kotlin.test.assertEquals import org.junit.Test +import kotlin.test.assertEquals /** * Created by Allan Wang on 2017-08-10. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/StringEscapeUtilsTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/StringEscapeUtilsTest.kt index 408dd214..f5ea56ae 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/utils/StringEscapeUtilsTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/StringEscapeUtilsTest.kt @@ -16,8 +16,8 @@ */ package com.pitchedapps.frost.utils -import kotlin.test.assertEquals import org.junit.Test +import kotlin.test.assertEquals /** * Created by Allan Wang on 11/03/18. diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt index ac6068ef..485d0a7a 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTests.kt @@ -17,10 +17,10 @@ package com.pitchedapps.frost.utils import com.pitchedapps.frost.facebook.FACEBOOK_COM +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -import org.junit.Test /** * Created by Allan Wang on 2017-11-15. diff --git a/build.gradle b/build.gradle index 0a7aae8a..013222fb 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ buildscript { classpath kau.Plugins.kotlin classpath kau.Plugins.spotless classpath kau.Plugins.dexCount + classpath kau.Plugins.hilt classpath kau.Plugins.gitVersion } diff --git a/spotless.gradle b/spotless.gradle index 29474895..16e1ff1a 100644 --- a/spotless.gradle +++ b/spotless.gradle @@ -3,7 +3,7 @@ apply plugin: "com.diffplug.spotless" spotless { kotlin { target "**/*.kt" - ktlint() + ktlint("0.41.0").userData(["disabled_rules": "no-wildcard-imports"]) licenseHeaderFile '../spotless.license.kt' trimTrailingWhitespace() endWithNewline() |