diff --git a/WORKSPACE b/WORKSPACE index 2795a030..257e6fdb 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -194,6 +194,9 @@ maven_install( "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0", "androidx.lifecycle:lifecycle-service:2.2.0", "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0", + "org.kodein.di:kodein-di:7.11.0", + "org.kodein.di:kodein-di-framework-android-x:7.11.0", + "org.kodein.di:kodein-di-framework-android-x-viewmodel:7.11.0", ], repositories = DAGGER_REPOSITORIES + [ "https://repo1.maven.org/maven2/", diff --git a/atox/build.gradle.kts b/atox/build.gradle.kts index 02c3df17..189c6792 100644 --- a/atox/build.gradle.kts +++ b/atox/build.gradle.kts @@ -22,7 +22,11 @@ android { } getByName("release") { isMinifyEnabled = true - proguardFiles("proguard-tox4j.pro", getDefaultProguardFile("proguard-android-optimize.txt")) + proguardFiles( + "proguard-tox4j.pro", + "proguard-kodein.pro", + getDefaultProguardFile("proguard-android-optimize.txt") + ) } } signingConfigs { @@ -72,6 +76,10 @@ dependencies { implementation(libs.androidx.lifecycle.service) implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.kodein.di) + implementation(libs.kodein.di.framework.android.x) + implementation(libs.kodein.di.framework.android.x.viewmodel) + implementation(libs.google.dagger.core) kapt(libs.google.dagger.compiler) diff --git a/atox/proguard-kodein.pro b/atox/proguard-kodein.pro new file mode 100644 index 00000000..4d3d575a --- /dev/null +++ b/atox/proguard-kodein.pro @@ -0,0 +1,5 @@ +-keep, allowobfuscation, allowoptimization class org.kodein.type.TypeReference +-keep, allowobfuscation, allowoptimization class org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest + +-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.TypeReference +-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest diff --git a/atox/src/androidTest/kotlin/IntegrationTest.kt b/atox/src/androidTest/kotlin/IntegrationTest.kt index d3a793c1..b8c513b1 100644 --- a/atox/src/androidTest/kotlin/IntegrationTest.kt +++ b/atox/src/androidTest/kotlin/IntegrationTest.kt @@ -5,7 +5,6 @@ package ltd.evilcorp.atox import android.app.Activity -import android.content.Context import androidx.room.Room import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click @@ -19,18 +18,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule -import dagger.BindsInstance -import dagger.Component -import dagger.Module -import dagger.Provides -import io.mockk.every -import io.mockk.mockk -import javax.inject.Singleton -import ltd.evilcorp.atox.di.AndroidModule -import ltd.evilcorp.atox.di.AppComponent -import ltd.evilcorp.atox.di.AppModule -import ltd.evilcorp.atox.di.DaoModule -import ltd.evilcorp.atox.di.ViewModelModule import ltd.evilcorp.core.db.Database import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.SaveManager @@ -38,6 +25,10 @@ import org.hamcrest.core.AllOf.allOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.instance +import org.kodein.di.singleton class InjectedActivityTestRule( activityClass: Class, @@ -49,36 +40,15 @@ class InjectedActivityTestRule( } } -@Module -class TestModule { - @Singleton - @Provides - fun provideDatabase(appContext: Context): Database = - Room.inMemoryDatabaseBuilder(appContext, Database::class.java).build() - - @Provides - fun provideSaveManager(): SaveManager = mockk(relaxUnitFun = true) { - every { list() } returns listOf("workaround") // mockk crashes w/ `listOf()`. - every { load(PublicKey("workaround")) } returns null - // Am I using mockk wrong or something? `every { save(any(), any() }` crashes. - } +class SaveManagerImplMock : SaveManager { + override fun list(): List = listOf("workaround") + override fun save(publicKey: PublicKey, saveData: ByteArray) {} + override fun load(publicKey: PublicKey): ByteArray? = null } -@Singleton -@Component( - modules = [ - AppModule::class, - AndroidModule::class, - TestModule::class, - DaoModule::class, - ViewModelModule::class - ] -) -interface TestComponent : AppComponent { - @Component.Factory - interface Factory { - fun create(@BindsInstance appContext: Context): AppComponent - } +fun testModule() = DI.Module("TestModule") { + bind(overrides = true) { singleton { Room.inMemoryDatabaseBuilder(instance(), Database::class.java).build() } } + bind(overrides = true) { singleton { SaveManagerImplMock() } } } @RunWith(AndroidJUnit4::class) @@ -87,7 +57,7 @@ class IntegrationTest { val activityRule = InjectedActivityTestRule(MainActivity::class.java) { val instrumentation = InstrumentationRegistry.getInstrumentation() val app = instrumentation.targetContext.applicationContext as App - app.componentOverride = DaggerTestComponent.factory().create(app) + app.testModule = testModule() } @Test diff --git a/atox/src/main/kotlin/ActionReceiver.kt b/atox/src/main/kotlin/ActionReceiver.kt index 075e134b..86ce9d28 100644 --- a/atox/src/main/kotlin/ActionReceiver.kt +++ b/atox/src/main/kotlin/ActionReceiver.kt @@ -12,7 +12,6 @@ import android.util.Log import android.widget.Toast import androidx.core.app.RemoteInput import im.tox.tox4j.av.exceptions.ToxavAnswerException -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.firstOrNull @@ -28,6 +27,9 @@ import ltd.evilcorp.domain.feature.ChatManager import ltd.evilcorp.domain.feature.ContactManager import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance const val KEY_TEXT_REPLY = "key_text_reply" const val KEY_CONTACT_PK = "key_contact_pk" @@ -43,30 +45,19 @@ enum class Action { private const val TAG = "ActionReceiver" -class ActionReceiver : BroadcastReceiver() { - @Inject - lateinit var callManager: CallManager +class ActionReceiver : BroadcastReceiver(), DIAware { + override lateinit var di: DI - @Inject - lateinit var chatManager: ChatManager - - @Inject - lateinit var contactManager: ContactManager - - @Inject - lateinit var contactRepository: ContactRepository - - @Inject - lateinit var notificationHelper: NotificationHelper - - @Inject - lateinit var tox: Tox - - @Inject - lateinit var scope: CoroutineScope + private val callManager: CallManager by instance() + private val chatManager: ChatManager by instance() + private val contactManager: ContactManager by instance() + private val contactRepository: ContactRepository by instance() + private val notificationHelper: NotificationHelper by instance() + private val tox: Tox by instance() + private val scope: CoroutineScope by instance() override fun onReceive(context: Context, intent: Intent) { - (context.applicationContext as App).component.inject(this) + di = (context.applicationContext as DIAware).di scope.launch { val pk = intent.getStringExtra(KEY_CONTACT_PK)?.let { PublicKey(it) } diff --git a/atox/src/main/kotlin/App.kt b/atox/src/main/kotlin/App.kt index 15be0c3d..8efddcec 100644 --- a/atox/src/main/kotlin/App.kt +++ b/atox/src/main/kotlin/App.kt @@ -4,16 +4,32 @@ package ltd.evilcorp.atox -import androidx.annotation.VisibleForTesting import androidx.multidex.MultiDexApplication -import ltd.evilcorp.atox.di.AppComponent -import ltd.evilcorp.atox.di.DaggerAppComponent +import ltd.evilcorp.atox.di.appModule +import ltd.evilcorp.atox.di.coreModule +import ltd.evilcorp.atox.di.daoModule +import ltd.evilcorp.atox.di.databaseModule +import ltd.evilcorp.atox.di.domainModule +import ltd.evilcorp.atox.di.viewModelModule +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.android.x.androidXModule +import org.kodein.di.diContext -class App : MultiDexApplication() { - val component: AppComponent by lazy { - componentOverride ?: DaggerAppComponent.factory().create(applicationContext) - } +class App : MultiDexApplication(), DIAware { + var testModule: DI.Module? = null + + override val di by DI.lazy { + importOnce(androidXModule(this@App)) + importOnce(appModule(diContext(this@App))) + importOnce(coreModule()) + importOnce(daoModule()) + importOnce(databaseModule()) + importOnce(domainModule(diContext(this@App))) + importOnce(viewModelModule(this@App)) - @VisibleForTesting - var componentOverride: AppComponent? = null + testModule?.let { + import(it, allowOverride = true) + } + } } diff --git a/atox/src/main/kotlin/AutoAway.kt b/atox/src/main/kotlin/AutoAway.kt index 2acfe266..3e77034d 100644 --- a/atox/src/main/kotlin/AutoAway.kt +++ b/atox/src/main/kotlin/AutoAway.kt @@ -6,8 +6,6 @@ package ltd.evilcorp.atox import android.util.Log import java.util.Timer -import javax.inject.Inject -import javax.inject.Singleton import kotlin.concurrent.schedule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -15,16 +13,18 @@ import ltd.evilcorp.atox.settings.Settings import ltd.evilcorp.core.vo.UserStatus import ltd.evilcorp.domain.feature.UserManager import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance private const val TAG = "AutoAway" -@Singleton -class AutoAway @Inject constructor( - private val scope: CoroutineScope, - private val settings: Settings, - private val userManager: UserManager, - private val tox: Tox -) { +class AutoAway(override val di: DI) : DIAware { + private val scope: CoroutineScope by instance() + private val settings: Settings by instance() + private val userManager: UserManager by instance() + private val tox: Tox by instance() + private var awayTimer = Timer() private var autoAway = false diff --git a/atox/src/main/kotlin/BootReceiver.kt b/atox/src/main/kotlin/BootReceiver.kt index 78c97da1..db0e584c 100644 --- a/atox/src/main/kotlin/BootReceiver.kt +++ b/atox/src/main/kotlin/BootReceiver.kt @@ -11,19 +11,22 @@ import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.navigation.NavDeepLinkBuilder -import javax.inject.Inject import ltd.evilcorp.atox.tox.ToxStarter import ltd.evilcorp.domain.tox.ToxSaveStatus +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance private const val ENCRYPTED = "aTox profile encrypted" -class BootReceiver : BroadcastReceiver() { - @Inject - lateinit var toxStarter: ToxStarter +class BootReceiver : BroadcastReceiver(), DIAware { + override lateinit var di: DI + + private val toxStarter: ToxStarter by instance() override fun onReceive(context: Context, intent: Intent) { if (intent.action == Intent.ACTION_BOOT_COMPLETED) { - (context.applicationContext as App).component.inject(this) + di = (context.applicationContext as DIAware).di if (toxStarter.tryLoadTox(null) == ToxSaveStatus.Encrypted) { val channel = NotificationChannelCompat.Builder(ENCRYPTED, NotificationManagerCompat.IMPORTANCE_HIGH) .setName(context.getString(R.string.atox_profile_locked)) diff --git a/atox/src/main/kotlin/Extensions.kt b/atox/src/main/kotlin/Extensions.kt index d512dd9b..81afee14 100644 --- a/atox/src/main/kotlin/Extensions.kt +++ b/atox/src/main/kotlin/Extensions.kt @@ -8,14 +8,10 @@ import android.content.Context import android.content.pm.PackageManager import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment -import ltd.evilcorp.atox.di.ViewModelFactory fun Context.hasPermission(permission: String) = ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -val Fragment.vmFactory: ViewModelFactory - get() = (requireActivity() as MainActivity).vmFactory - class NoSuchArgumentException(arg: String) : Exception("No such argument: $arg") fun Fragment.requireStringArg(key: String) = diff --git a/atox/src/main/kotlin/MainActivity.kt b/atox/src/main/kotlin/MainActivity.kt index 2f7aebdf..6cd40dad 100644 --- a/atox/src/main/kotlin/MainActivity.kt +++ b/atox/src/main/kotlin/MainActivity.kt @@ -14,28 +14,23 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.bundleOf import androidx.core.view.WindowCompat import androidx.navigation.fragment.findNavController -import javax.inject.Inject -import ltd.evilcorp.atox.di.ViewModelFactory import ltd.evilcorp.atox.settings.Settings import ltd.evilcorp.atox.ui.contactlist.ARG_SHARE +import org.kodein.di.DIAware +import org.kodein.di.android.closestDI +import org.kodein.di.instance private const val TAG = "MainActivity" private const val SCHEME = "tox:" private const val TOX_ID_LENGTH = 76 -class MainActivity : AppCompatActivity() { - @Inject - lateinit var vmFactory: ViewModelFactory +class MainActivity : AppCompatActivity(), DIAware { + override val di by closestDI() - @Inject - lateinit var autoAway: AutoAway - - @Inject - lateinit var settings: Settings + private val autoAway: AutoAway by instance() + private val settings: Settings by instance() override fun onCreate(savedInstanceState: Bundle?) { - (application as App).component.inject(this) - super.onCreate(savedInstanceState) if (settings.disableScreenshots) { diff --git a/atox/src/main/kotlin/ToxService.kt b/atox/src/main/kotlin/ToxService.kt index c4db0248..55fd9a4a 100644 --- a/atox/src/main/kotlin/ToxService.kt +++ b/atox/src/main/kotlin/ToxService.kt @@ -16,7 +16,6 @@ import androidx.lifecycle.LifecycleService import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import java.util.Timer -import javax.inject.Inject import kotlin.concurrent.schedule import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter @@ -27,12 +26,21 @@ import ltd.evilcorp.core.repository.UserRepository import ltd.evilcorp.core.vo.ConnectionStatus import ltd.evilcorp.domain.tox.Tox import ltd.evilcorp.domain.tox.ToxSaveStatus +import org.kodein.di.DIAware +import org.kodein.di.android.closestDI +import org.kodein.di.instance private const val TAG = "ToxService" private const val NOTIFICATION_ID = 1984 private const val BOOTSTRAP_INTERVAL_MS = 60_000L -class ToxService : LifecycleService() { +class ToxService : LifecycleService(), DIAware { + override val di by closestDI() + + private val tox: Tox by instance() + private val toxStarter: ToxStarter by instance() + private val userRepository: UserRepository by instance() + private val channelId = "ToxService" private var connectionStatus: ConnectionStatus? = null @@ -40,15 +48,6 @@ class ToxService : LifecycleService() { private val notifier by lazy { NotificationManagerCompat.from(this) } private var bootstrapTimer = Timer() - @Inject - lateinit var tox: Tox - - @Inject - lateinit var toxStarter: ToxStarter - - @Inject - lateinit var userRepository: UserRepository - private fun createNotificationChannel() { val channel = NotificationChannelCompat.Builder(channelId, NotificationManagerCompat.IMPORTANCE_LOW) .setName("Tox Service") @@ -79,8 +78,6 @@ class ToxService : LifecycleService() { } override fun onCreate() { - (application as App).component.inject(this) - super.onCreate() if (!tox.started) { diff --git a/atox/src/main/kotlin/di/AndroidModule.kt b/atox/src/main/kotlin/di/AndroidModule.kt deleted file mode 100644 index 20920793..00000000 --- a/atox/src/main/kotlin/di/AndroidModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2020 aTox contributors -// -// SPDX-License-Identifier: GPL-3.0-only - -package ltd.evilcorp.atox.di - -import android.content.ContentResolver -import android.content.Context -import dagger.Module -import dagger.Provides - -@Module -class AndroidModule { - @Provides - fun provideContentResolver(context: Context): ContentResolver = context.contentResolver -} diff --git a/atox/src/main/kotlin/di/AppComponent.kt b/atox/src/main/kotlin/di/AppComponent.kt deleted file mode 100644 index 84d37de2..00000000 --- a/atox/src/main/kotlin/di/AppComponent.kt +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2021 aTox contributors -// -// SPDX-License-Identifier: GPL-3.0-only - -package ltd.evilcorp.atox.di - -import android.content.Context -import dagger.BindsInstance -import dagger.Component -import javax.inject.Singleton -import ltd.evilcorp.atox.ActionReceiver -import ltd.evilcorp.atox.BootReceiver -import ltd.evilcorp.atox.MainActivity -import ltd.evilcorp.atox.ToxService - -@Singleton -@Component( - modules = [ - AndroidModule::class, - AppModule::class, - DatabaseModule::class, - DaoModule::class, - ViewModelModule::class - ] -) -interface AppComponent { - @Component.Factory - interface Factory { - fun create(@BindsInstance appContext: Context): AppComponent - } - - fun inject(activity: MainActivity) - fun inject(service: ToxService) - fun inject(receiver: BootReceiver) - fun inject(receiver: ActionReceiver) -} diff --git a/atox/src/main/kotlin/di/AppModule.kt b/atox/src/main/kotlin/di/AppModule.kt index cc86d9f3..a5d0a324 100644 --- a/atox/src/main/kotlin/di/AppModule.kt +++ b/atox/src/main/kotlin/di/AppModule.kt @@ -4,18 +4,27 @@ package ltd.evilcorp.atox.di -import dagger.Module -import dagger.Provides import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import ltd.evilcorp.atox.AutoAway +import ltd.evilcorp.atox.settings.Settings import ltd.evilcorp.atox.tox.BootstrapNodeRegistryImpl +import ltd.evilcorp.atox.tox.EventListenerCallbacks +import ltd.evilcorp.atox.tox.ToxStarter +import ltd.evilcorp.atox.ui.NotificationHelper import ltd.evilcorp.domain.tox.BootstrapNodeRegistry +import org.kodein.di.DI +import org.kodein.di.DIContext +import org.kodein.di.bind +import org.kodein.di.provider +import org.kodein.di.singleton -@Module -class AppModule { - @Provides - fun provideBootstrapNodeRegistry(nodeRegistry: BootstrapNodeRegistryImpl): BootstrapNodeRegistry = nodeRegistry - - @Provides - fun provideCoroutineScope(): CoroutineScope = CoroutineScope(Dispatchers.Default) +fun appModule(appContext: DIContext<*>) = DI.Module(name = "AppModule") { + bind { singleton { BootstrapNodeRegistryImpl(di) } } + bind { singleton { AutoAway(di) } } + bind { singleton { EventListenerCallbacks(di, appContext) } } + bind { singleton { NotificationHelper(di) } } + bind { singleton { Settings(di, appContext) } } + bind { singleton { ToxStarter(di) } } + bind { provider { CoroutineScope(Dispatchers.Default) } } } diff --git a/atox/src/main/kotlin/di/CoreModule.kt b/atox/src/main/kotlin/di/CoreModule.kt new file mode 100644 index 00000000..e4802ddc --- /dev/null +++ b/atox/src/main/kotlin/di/CoreModule.kt @@ -0,0 +1,20 @@ +package ltd.evilcorp.atox.di + +import ltd.evilcorp.core.repository.ContactRepository +import ltd.evilcorp.core.repository.FileTransferRepository +import ltd.evilcorp.core.repository.FriendRequestRepository +import ltd.evilcorp.core.repository.MessageRepository +import ltd.evilcorp.core.repository.UserRepository +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.singleton + +fun coreModule() = DI.Module(name = "CoreModule") { + importOnce(databaseModule()) + + bind { singleton { ContactRepository(di) } } + bind { singleton { FileTransferRepository(di) } } + bind { singleton { FriendRequestRepository(di) } } + bind { singleton { MessageRepository(di) } } + bind { singleton { UserRepository(di) } } +} diff --git a/atox/src/main/kotlin/di/DatabaseModule.kt b/atox/src/main/kotlin/di/DatabaseModule.kt index 727f3038..1ec7e9b9 100644 --- a/atox/src/main/kotlin/di/DatabaseModule.kt +++ b/atox/src/main/kotlin/di/DatabaseModule.kt @@ -4,50 +4,28 @@ package ltd.evilcorp.atox.di -import android.content.Context import androidx.room.Room -import dagger.Module -import dagger.Provides -import javax.inject.Singleton import ltd.evilcorp.core.db.ALL_MIGRATIONS -import ltd.evilcorp.core.db.ContactDao import ltd.evilcorp.core.db.Database -import ltd.evilcorp.core.db.FileTransferDao -import ltd.evilcorp.core.db.FriendRequestDao -import ltd.evilcorp.core.db.MessageDao -import ltd.evilcorp.core.db.UserDao - -@Suppress("unused") -@Module -class DatabaseModule { - @Singleton - @Provides - fun provideDatabase(appContext: Context): Database = - Room.databaseBuilder(appContext, Database::class.java, "core_db") - .addMigrations(*ALL_MIGRATIONS) - .build() +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.instance +import org.kodein.di.singleton + +fun databaseModule() = DI.Module(name = "DatabaseModule") { + bind { + singleton { + Room.databaseBuilder(instance(), Database::class.java, "core_db") + .addMigrations(*ALL_MIGRATIONS) + .build() + } + } } -@Suppress("unused") -@Module -class DaoModule { - @Singleton - @Provides - internal fun provideContactDao(db: Database): ContactDao = db.contactDao() - - @Singleton - @Provides - internal fun provideFileTransferDao(db: Database): FileTransferDao = db.fileTransferDao() - - @Singleton - @Provides - internal fun provideFriendRequestDao(db: Database): FriendRequestDao = db.friendRequestDao() - - @Singleton - @Provides - internal fun provideMessageDao(db: Database): MessageDao = db.messageDao() - - @Singleton - @Provides - internal fun provideUserDao(db: Database): UserDao = db.userDao() +fun daoModule() = DI.Module(name = "DaoModule") { + bind { singleton { instance().contactDao() } } + bind { singleton { instance().fileTransferDao() } } + bind { singleton { instance().friendRequestDao() } } + bind { singleton { instance().messageDao() } } + bind { singleton { instance().userDao() } } } diff --git a/atox/src/main/kotlin/di/DomainModule.kt b/atox/src/main/kotlin/di/DomainModule.kt new file mode 100644 index 00000000..71eb338e --- /dev/null +++ b/atox/src/main/kotlin/di/DomainModule.kt @@ -0,0 +1,29 @@ +package ltd.evilcorp.atox.di + +import ltd.evilcorp.domain.feature.CallManager +import ltd.evilcorp.domain.feature.ChatManager +import ltd.evilcorp.domain.feature.ContactManager +import ltd.evilcorp.domain.feature.FileTransferManager +import ltd.evilcorp.domain.feature.FriendRequestManager +import ltd.evilcorp.domain.feature.UserManager +import ltd.evilcorp.domain.tox.SaveManager +import ltd.evilcorp.domain.tox.SaveManagerImpl +import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIContext +import org.kodein.di.bind +import org.kodein.di.singleton + +fun domainModule(appContext: DIContext<*>) = DI.Module(name = "DomainModule") { + importOnce(appModule(appContext)) + importOnce(coreModule()) + + bind { singleton { CallManager(di, appContext) } } + bind { singleton { ChatManager(di) } } + bind { singleton { ContactManager(di) } } + bind { singleton { FileTransferManager(di, appContext) } } + bind { singleton { FriendRequestManager(di) } } + bind { singleton { SaveManagerImpl(di, appContext) } } + bind { singleton { UserManager(di) } } + bind { singleton { Tox(di) } } +} diff --git a/atox/src/main/kotlin/di/ViewModelFactory.kt b/atox/src/main/kotlin/di/ViewModelFactory.kt deleted file mode 100644 index e871a104..00000000 --- a/atox/src/main/kotlin/di/ViewModelFactory.kt +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2021 aTox contributors -// -// SPDX-License-Identifier: GPL-3.0-only - -package ltd.evilcorp.atox.di - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import javax.inject.Inject -import javax.inject.Provider -import javax.inject.Singleton - -@Suppress("UNCHECKED_CAST") -@Singleton -class ViewModelFactory @Inject constructor( - private val creators: Map, @JvmSuppressWildcards Provider> -) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - val creator = creators[modelClass] ?: creators.entries.first { - modelClass.isAssignableFrom(it.key) - }.value - - return creator.get() as T - } -} diff --git a/atox/src/main/kotlin/di/ViewModelModule.kt b/atox/src/main/kotlin/di/ViewModelModule.kt index 94088ad5..106d8b74 100644 --- a/atox/src/main/kotlin/di/ViewModelModule.kt +++ b/atox/src/main/kotlin/di/ViewModelModule.kt @@ -4,12 +4,7 @@ package ltd.evilcorp.atox.di -import androidx.lifecycle.ViewModel -import dagger.Binds -import dagger.MapKey -import dagger.Module -import dagger.multibindings.IntoMap -import kotlin.reflect.KClass +import ltd.evilcorp.atox.App import ltd.evilcorp.atox.ui.addcontact.AddContactViewModel import ltd.evilcorp.atox.ui.call.CallViewModel import ltd.evilcorp.atox.ui.chat.ChatViewModel @@ -19,62 +14,23 @@ import ltd.evilcorp.atox.ui.create_profile.CreateProfileViewModel import ltd.evilcorp.atox.ui.friend_request.FriendRequestViewModel import ltd.evilcorp.atox.ui.settings.SettingsViewModel import ltd.evilcorp.atox.ui.user_profile.UserProfileViewModel - -@MustBeDocumented -@Target( - AnnotationTarget.FUNCTION, - AnnotationTarget.PROPERTY_GETTER, - AnnotationTarget.PROPERTY_SETTER -) -@Retention(AnnotationRetention.RUNTIME) -@MapKey -annotation class ViewModelKey(val value: KClass) - -@Suppress("unused") -@Module -abstract class ViewModelModule { - @Binds - @IntoMap - @ViewModelKey(AddContactViewModel::class) - abstract fun bindAddContactViewModel(vm: AddContactViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(CallViewModel::class) - abstract fun bindCallViewModel(vm: CallViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(ChatViewModel::class) - abstract fun bindChatViewModel(vm: ChatViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(ContactListViewModel::class) - abstract fun bindContactListViewModel(vm: ContactListViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(ContactProfileViewModel::class) - abstract fun bindContactProfileViewModel(vm: ContactProfileViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(FriendRequestViewModel::class) - abstract fun bindFriendRequestViewModel(vm: FriendRequestViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(CreateProfileViewModel::class) - abstract fun bindProfileViewModel(vm: CreateProfileViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(SettingsViewModel::class) - abstract fun bindSettingsViewModel(vm: SettingsViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(UserProfileViewModel::class) - abstract fun bindUserProfileViewModel(vm: UserProfileViewModel): ViewModel +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.diContext +import org.kodein.di.provider + +fun viewModelModule(app: App) = DI.Module(name = "ViewModelModule") { + importOnce(appModule(diContext(app))) + importOnce(domainModule(diContext(app))) + importOnce(coreModule()) + + bind { provider { AddContactViewModel(app) } } + bind { provider { CallViewModel(app) } } + bind { provider { ChatViewModel(app) } } + bind { provider { ContactListViewModel(app) } } + bind { provider { ContactProfileViewModel(app) } } + bind { provider { CreateProfileViewModel(app) } } + bind { provider { FriendRequestViewModel(app) } } + bind { provider { SettingsViewModel(app) } } + bind { provider { UserProfileViewModel(app) } } } diff --git a/atox/src/main/kotlin/settings/Settings.kt b/atox/src/main/kotlin/settings/Settings.kt index ed9890b9..91512950 100644 --- a/atox/src/main/kotlin/settings/Settings.kt +++ b/atox/src/main/kotlin/settings/Settings.kt @@ -6,13 +6,16 @@ package ltd.evilcorp.atox.settings import android.content.ComponentName import android.content.Context +import android.content.SharedPreferences import android.content.pm.PackageManager import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.edit -import androidx.preference.PreferenceManager -import javax.inject.Inject import ltd.evilcorp.atox.BootReceiver import ltd.evilcorp.domain.tox.ProxyType +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.DIContext +import org.kodein.di.instance enum class FtAutoAccept { None, @@ -25,8 +28,10 @@ enum class BootstrapNodeSource { UserProvided, } -class Settings @Inject constructor(private val ctx: Context) { - private val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) +class Settings(override val di: DI, override val diContext: DIContext<*>) : DIAware { + private val ctx: Context by instance() + private val packageManager: PackageManager by instance() + private val preferences: SharedPreferences by instance() var theme: Int get() = preferences.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) @@ -40,7 +45,7 @@ class Settings @Inject constructor(private val ctx: Context) { set(enabled) = preferences.edit().putBoolean("udp_enabled", enabled).apply() var runAtStartup: Boolean - get() = ctx.packageManager.getComponentEnabledSetting( + get() = packageManager.getComponentEnabledSetting( ComponentName(ctx, BootReceiver::class.java) ) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED set(runAtStartup) { @@ -50,7 +55,7 @@ class Settings @Inject constructor(private val ctx: Context) { PackageManager.COMPONENT_ENABLED_STATE_DISABLED } - ctx.packageManager.setComponentEnabledSetting( + packageManager.setComponentEnabledSetting( ComponentName(ctx, BootReceiver::class.java), state, PackageManager.DONT_KILL_APP diff --git a/atox/src/main/kotlin/tox/BootstrapNodeRegistryImpl.kt b/atox/src/main/kotlin/tox/BootstrapNodeRegistryImpl.kt index b65da6e7..174638ba 100644 --- a/atox/src/main/kotlin/tox/BootstrapNodeRegistryImpl.kt +++ b/atox/src/main/kotlin/tox/BootstrapNodeRegistryImpl.kt @@ -7,8 +7,6 @@ package ltd.evilcorp.atox.tox import android.content.Context import android.widget.Toast import java.io.File -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -18,15 +16,16 @@ import ltd.evilcorp.atox.settings.Settings import ltd.evilcorp.domain.tox.BootstrapNode import ltd.evilcorp.domain.tox.BootstrapNodeJsonParser import ltd.evilcorp.domain.tox.BootstrapNodeRegistry +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance -@Singleton -class BootstrapNodeRegistryImpl @Inject constructor( - private val scope: CoroutineScope, - private val context: Context, - private val parser: BootstrapNodeJsonParser, - private val settings: Settings, -) : BootstrapNodeRegistry { - private lateinit var nodes: List +class BootstrapNodeRegistryImpl(override val di: DI) : BootstrapNodeRegistry, DIAware { + private val scope: CoroutineScope by instance() + private val context: Context by instance() + private val settings: Settings by instance() + + private var nodes: List = listOf() init { reset() @@ -40,7 +39,7 @@ class BootstrapNodeRegistryImpl @Inject constructor( File(context.filesDir, "user_nodes.json").readBytes().decodeToString() } - nodes = parser.parse(str) + nodes = BootstrapNodeJsonParser.parse(str) if (nodes.isEmpty()) { Toast.makeText(context, context.getString(R.string.error_no_nodes_loaded), Toast.LENGTH_LONG).show() } diff --git a/atox/src/main/kotlin/tox/EventListenerCallbacks.kt b/atox/src/main/kotlin/tox/EventListenerCallbacks.kt index ab2ce288..2c779e42 100644 --- a/atox/src/main/kotlin/tox/EventListenerCallbacks.kt +++ b/atox/src/main/kotlin/tox/EventListenerCallbacks.kt @@ -4,14 +4,12 @@ package ltd.evilcorp.atox.tox -import android.content.Context +import android.content.res.Resources import android.util.Log import im.tox.tox4j.av.enums.ToxavFriendCallState import im.tox.tox4j.core.enums.ToxFileControl import java.net.URLConnection import java.util.Date -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.firstOrNull @@ -41,6 +39,10 @@ import ltd.evilcorp.domain.tox.Tox import ltd.evilcorp.domain.tox.ToxAvEventListener import ltd.evilcorp.domain.tox.ToxEventListener import ltd.evilcorp.domain.tox.toMessageType +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.DIContext +import org.kodein.di.instance private const val TAG = "EventListenerCallbacks" @@ -54,20 +56,19 @@ private fun isImage(filename: String) = try { private const val FINGERPRINT_LEN = 8 private fun String.fingerprint() = this.take(FINGERPRINT_LEN) -@Singleton -class EventListenerCallbacks @Inject constructor( - private val ctx: Context, - private val contactRepository: ContactRepository, - private val friendRequestRepository: FriendRequestRepository, - private val messageRepository: MessageRepository, - private val userRepository: UserRepository, - private val callManager: CallManager, - private val chatManager: ChatManager, - private val fileTransferManager: FileTransferManager, - private val notificationHelper: NotificationHelper, - private val tox: Tox, - private val settings: Settings, -) { +class EventListenerCallbacks(override val di: DI, override val diContext: DIContext<*>) : DIAware { + private val resources: Resources by instance() + private val contactRepository: ContactRepository by instance() + private val friendRequestRepository: FriendRequestRepository by instance() + private val messageRepository: MessageRepository by instance() + private val userRepository: UserRepository by instance() + private val callManager: CallManager by instance() + private val chatManager: ChatManager by instance() + private val fileTransferManager: FileTransferManager by instance() + private val notificationHelper: NotificationHelper by instance() + private val tox: Tox by instance() + private val settings: Settings by instance() + private var audioPlayer: AudioPlayer? = null private val scope = CoroutineScope(Dispatchers.Default) @@ -80,7 +81,7 @@ class EventListenerCallbacks @Inject constructor( private fun notifyMessage(contact: Contact, message: String) = notificationHelper.showMessageNotification(contact, message, silent = tox.getStatus() == UserStatus.Busy) - fun setUp(listener: ToxEventListener) = with(listener) { + fun setUpToxEventListener() = with(ToxEventListener) { friendStatusMessageHandler = { publicKey, message -> contactRepository.setStatusMessage(publicKey, message) } @@ -144,7 +145,7 @@ class EventListenerCallbacks @Inject constructor( if (chatManager.activeChat != publicKey) { scope.launch { val contact = tryGetContact(publicKey, "FileRecv") ?: return@launch - val msg = ctx.getString(R.string.notification_file_transfer, name) + val msg = resources.getString(R.string.notification_file_transfer, name) notifyMessage(contact, msg) } contactRepository.setHasUnreadMessages(publicKey, true) @@ -174,7 +175,7 @@ class EventListenerCallbacks @Inject constructor( } } - fun setUp(listener: ToxAvEventListener) = with(listener) { + fun setUpToxAvEventListener() = with(ToxAvEventListener) { callHandler = { pk, audioEnabled, videoEnabled -> Log.e(TAG, "call ${pk.fingerprint()} $audioEnabled $videoEnabled") scope.launch { diff --git a/atox/src/main/kotlin/tox/ToxStarter.kt b/atox/src/main/kotlin/tox/ToxStarter.kt index 59967d71..6022dbcf 100644 --- a/atox/src/main/kotlin/tox/ToxStarter.kt +++ b/atox/src/main/kotlin/tox/ToxStarter.kt @@ -10,7 +10,6 @@ import android.os.Build import android.util.Log import im.tox.tox4j.core.exceptions.ToxNewException import im.tox.tox4j.crypto.exceptions.ToxDecryptionException -import javax.inject.Inject import ltd.evilcorp.atox.ToxService import ltd.evilcorp.atox.settings.Settings import ltd.evilcorp.domain.feature.FileTransferManager @@ -19,32 +18,31 @@ import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.SaveManager import ltd.evilcorp.domain.tox.SaveOptions import ltd.evilcorp.domain.tox.Tox -import ltd.evilcorp.domain.tox.ToxAvEventListener -import ltd.evilcorp.domain.tox.ToxEventListener import ltd.evilcorp.domain.tox.ToxSaveStatus import ltd.evilcorp.domain.tox.testToxSave +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance private const val TAG = "ToxStarter" -class ToxStarter @Inject constructor( - private val fileTransferManager: FileTransferManager, - private val saveManager: SaveManager, - private val userManager: UserManager, - private val listenerCallbacks: EventListenerCallbacks, - private val tox: Tox, - private val eventListener: ToxEventListener, - private val avEventListener: ToxAvEventListener, - private val context: Context, - private val settings: Settings, -) { +class ToxStarter(override val di: DI) : DIAware { + private val fileTransferManager: FileTransferManager by instance() + private val saveManager: SaveManager by instance() + private val userManager: UserManager by instance() + private val listenerCallbacks: EventListenerCallbacks by instance() + private val tox: Tox by instance() + private val context: Context by instance() + private val settings: Settings by instance() + fun startTox(save: ByteArray? = null, password: String? = null): ToxSaveStatus { - listenerCallbacks.setUp(eventListener) - listenerCallbacks.setUp(avEventListener) + listenerCallbacks.setUpToxEventListener() + listenerCallbacks.setUpToxAvEventListener() val options = SaveOptions(save, settings.udpEnabled, settings.proxyType, settings.proxyAddress, settings.proxyPort) try { tox.isBootstrapNeeded = true - tox.start(options, password, eventListener, avEventListener) + tox.start(options, password) } catch (e: ToxNewException) { Log.e(TAG, e.message) return testToxSave(options, password) diff --git a/atox/src/main/kotlin/ui/BaseFragment.kt b/atox/src/main/kotlin/ui/BaseFragment.kt index 4525fe7d..3517f4b2 100644 --- a/atox/src/main/kotlin/ui/BaseFragment.kt +++ b/atox/src/main/kotlin/ui/BaseFragment.kt @@ -10,10 +10,14 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.viewbinding.ViewBinding +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI abstract class BaseFragment( private val inflate: (LayoutInflater, ViewGroup?, Boolean) -> T -) : Fragment() { +) : Fragment(), DIAware { + override val di by closestDI() + private var _binding: T? = null val binding get() = _binding!! diff --git a/atox/src/main/kotlin/ui/NotificationHelper.kt b/atox/src/main/kotlin/ui/NotificationHelper.kt index 47c82e31..cb52a904 100644 --- a/atox/src/main/kotlin/ui/NotificationHelper.kt +++ b/atox/src/main/kotlin/ui/NotificationHelper.kt @@ -28,8 +28,6 @@ import androidx.core.os.bundleOf import androidx.navigation.NavDeepLinkBuilder import com.squareup.picasso.Picasso import com.squareup.picasso.Transformation -import javax.inject.Inject -import javax.inject.Singleton import ltd.evilcorp.atox.Action import ltd.evilcorp.atox.ActionReceiver import ltd.evilcorp.atox.KEY_ACTION @@ -43,15 +41,17 @@ import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.core.vo.FriendRequest import ltd.evilcorp.core.vo.UserStatus import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance private const val MESSAGE = "aTox messages" private const val FRIEND_REQUEST = "aTox friend requests" private const val CALL = "aTox calls" -@Singleton -class NotificationHelper @Inject constructor( - private val context: Context -) { +class NotificationHelper(override val di: DI) : DIAware { + private val context: Context by instance() + private val notifier = NotificationManagerCompat.from(context) private val notifierOld = context.getSystemService()!! diff --git a/atox/src/main/kotlin/ui/StatusDialog.kt b/atox/src/main/kotlin/ui/StatusDialog.kt index 5d0522c8..eda5b05a 100644 --- a/atox/src/main/kotlin/ui/StatusDialog.kt +++ b/atox/src/main/kotlin/ui/StatusDialog.kt @@ -11,11 +11,9 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.TransitionDrawable import android.os.Bundle import android.view.Window -import javax.inject.Inject import ltd.evilcorp.atox.R import ltd.evilcorp.atox.databinding.DialogStatusBinding import ltd.evilcorp.core.vo.UserStatus -import ltd.evilcorp.domain.feature.UserManager private const val TRANSITION_TIME = 250 @@ -24,9 +22,6 @@ class StatusDialog( private var activeStatus: UserStatus, private val setStatusFunc: (UserStatus) -> Unit ) : Dialog(ctx, R.style.DialogSlideAnimation) { - @Inject - lateinit var userManager: UserManager - private var _binding: DialogStatusBinding? = null private val binding get() = _binding!! diff --git a/atox/src/main/kotlin/ui/addcontact/AddContactFragment.kt b/atox/src/main/kotlin/ui/addcontact/AddContactFragment.kt index 5486543c..17e9bf6a 100644 --- a/atox/src/main/kotlin/ui/addcontact/AddContactFragment.kt +++ b/atox/src/main/kotlin/ui/addcontact/AddContactFragment.kt @@ -18,18 +18,17 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.updatePadding import androidx.core.widget.doAfterTextChanged -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import ltd.evilcorp.atox.R import ltd.evilcorp.atox.databinding.FragmentAddContactBinding import ltd.evilcorp.atox.ui.BaseFragment -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.domain.tox.ToxID import ltd.evilcorp.domain.tox.ToxIdValidator +import org.kodein.di.android.x.viewmodel.viewModel class AddContactFragment : BaseFragment(FragmentAddContactBinding::inflate) { - private val viewModel: AddContactViewModel by viewModels { vmFactory } + private val viewModel: AddContactViewModel by viewModel() private var toxIdValid: Boolean = false private var messageValid: Boolean = true diff --git a/atox/src/main/kotlin/ui/addcontact/AddContactViewModel.kt b/atox/src/main/kotlin/ui/addcontact/AddContactViewModel.kt index 08e1f7e7..42f04e09 100644 --- a/atox/src/main/kotlin/ui/addcontact/AddContactViewModel.kt +++ b/atox/src/main/kotlin/ui/addcontact/AddContactViewModel.kt @@ -4,22 +4,27 @@ package ltd.evilcorp.atox.ui.addcontact +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData -import javax.inject.Inject +import ltd.evilcorp.atox.App import ltd.evilcorp.atox.tox.ToxStarter import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.domain.feature.ContactManager import ltd.evilcorp.domain.tox.Tox import ltd.evilcorp.domain.tox.ToxID import ltd.evilcorp.domain.tox.ToxSaveStatus +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance + +class AddContactViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val contactManager: ContactManager by instance() + private val tox: Tox by instance() + private val toxStarter: ToxStarter by instance() -class AddContactViewModel @Inject constructor( - private val contactManager: ContactManager, - private val tox: Tox, - private val toxStarter: ToxStarter -) : ViewModel() { val toxId by lazy { tox.toxId } val contacts: LiveData> = contactManager.getAll().asLiveData() diff --git a/atox/src/main/kotlin/ui/call/CallFragment.kt b/atox/src/main/kotlin/ui/call/CallFragment.kt index a640a138..b3669d97 100644 --- a/atox/src/main/kotlin/ui/call/CallFragment.kt +++ b/atox/src/main/kotlin/ui/call/CallFragment.kt @@ -12,7 +12,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding -import androidx.fragment.app.viewModels import androidx.lifecycle.asLiveData import androidx.navigation.fragment.findNavController import ltd.evilcorp.atox.R @@ -21,14 +20,14 @@ import ltd.evilcorp.atox.hasPermission import ltd.evilcorp.atox.requireStringArg import ltd.evilcorp.atox.ui.BaseFragment import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.domain.feature.CallState import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.android.x.viewmodel.viewModel private const val PERMISSION = Manifest.permission.RECORD_AUDIO class CallFragment : BaseFragment(FragmentCallBinding::inflate) { - private val vm: CallViewModel by viewModels { vmFactory } + private val vm: CallViewModel by viewModel() private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() diff --git a/atox/src/main/kotlin/ui/call/CallViewModel.kt b/atox/src/main/kotlin/ui/call/CallViewModel.kt index e0d56f90..7a9ac49f 100644 --- a/atox/src/main/kotlin/ui/call/CallViewModel.kt +++ b/atox/src/main/kotlin/ui/call/CallViewModel.kt @@ -4,25 +4,30 @@ package ltd.evilcorp.atox.ui.call +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import ltd.evilcorp.atox.App import ltd.evilcorp.atox.ui.NotificationHelper import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.domain.feature.CallManager import ltd.evilcorp.domain.feature.ContactManager import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance + +class CallViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val scope: CoroutineScope by instance() + private val callManager: CallManager by instance() + private val notificationHelper: NotificationHelper by instance() + private val contactManager: ContactManager by instance() -class CallViewModel @Inject constructor( - private val scope: CoroutineScope, - private val callManager: CallManager, - private val notificationHelper: NotificationHelper, - private val contactManager: ContactManager, -) : ViewModel() { private var publicKey = PublicKey("") val contact: LiveData by lazy { diff --git a/atox/src/main/kotlin/ui/chat/ChatFragment.kt b/atox/src/main/kotlin/ui/chat/ChatFragment.kt index 34be2e5d..fcf54330 100644 --- a/atox/src/main/kotlin/ui/chat/ChatFragment.kt +++ b/atox/src/main/kotlin/ui/chat/ChatFragment.kt @@ -30,7 +30,6 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.updatePadding import androidx.core.widget.doAfterTextChanged -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.google.android.material.math.MathUtils.lerp import java.io.File @@ -43,7 +42,6 @@ import ltd.evilcorp.atox.databinding.FragmentChatBinding import ltd.evilcorp.atox.requireStringArg import ltd.evilcorp.atox.truncated import ltd.evilcorp.atox.ui.BaseFragment -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.ConnectionStatus import ltd.evilcorp.core.vo.FileTransfer import ltd.evilcorp.core.vo.Message @@ -51,6 +49,7 @@ import ltd.evilcorp.core.vo.MessageType import ltd.evilcorp.core.vo.isComplete import ltd.evilcorp.domain.feature.CallState import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.android.x.viewmodel.viewModel const val CONTACT_PUBLIC_KEY = "publicKey" const val FOCUS_ON_MESSAGE_BOX = "focusOnMessageBox" @@ -64,7 +63,7 @@ class OpenMultiplePersistableDocuments : ActivityResultContracts.OpenMultipleDoc } class ChatFragment : BaseFragment(FragmentChatBinding::inflate) { - private val viewModel: ChatViewModel by viewModels { vmFactory } + private val viewModel: ChatViewModel by viewModel() private lateinit var contactPubKey: String private var contactName = "" diff --git a/atox/src/main/kotlin/ui/chat/ChatViewModel.kt b/atox/src/main/kotlin/ui/chat/ChatViewModel.kt index 79990d34..3740b41d 100644 --- a/atox/src/main/kotlin/ui/chat/ChatViewModel.kt +++ b/atox/src/main/kotlin/ui/chat/ChatViewModel.kt @@ -9,16 +9,14 @@ import android.content.Context import android.net.Uri import android.util.Log import android.widget.Toast +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import com.squareup.picasso.Picasso import java.io.File import java.io.FileInputStream -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull @@ -26,6 +24,7 @@ import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.transform import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import ltd.evilcorp.atox.App import ltd.evilcorp.atox.R import ltd.evilcorp.atox.ui.NotificationHelper import ltd.evilcorp.core.vo.ConnectionStatus @@ -39,6 +38,9 @@ import ltd.evilcorp.domain.feature.ChatManager import ltd.evilcorp.domain.feature.ContactManager import ltd.evilcorp.domain.feature.FileTransferManager import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance private const val TAG = "ChatViewModel" @@ -48,16 +50,18 @@ enum class CallAvailability { Active, } -class ChatViewModel @Inject constructor( - private val callManager: CallManager, - private val chatManager: ChatManager, - private val contactManager: ContactManager, - private val fileTransferManager: FileTransferManager, - private val notificationHelper: NotificationHelper, - private val resolver: ContentResolver, - private val context: Context, - private val scope: CoroutineScope, -) : ViewModel() { +class ChatViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val callManager: CallManager by instance() + private val chatManager: ChatManager by instance() + private val contactManager: ContactManager by instance() + private val fileTransferManager: FileTransferManager by instance() + private val notificationHelper: NotificationHelper by instance() + private val resolver: ContentResolver by instance() + private val context: Context by instance() + private val scope: CoroutineScope by instance() + private var publicKey = PublicKey("") private var sentTyping = false diff --git a/atox/src/main/kotlin/ui/contact_profile/ContactProfileFragment.kt b/atox/src/main/kotlin/ui/contact_profile/ContactProfileFragment.kt index f623806b..ca97ca87 100644 --- a/atox/src/main/kotlin/ui/contact_profile/ContactProfileFragment.kt +++ b/atox/src/main/kotlin/ui/contact_profile/ContactProfileFragment.kt @@ -9,18 +9,17 @@ import android.view.View import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding -import androidx.fragment.app.viewModels import ltd.evilcorp.atox.R import ltd.evilcorp.atox.databinding.FragmentContactProfileBinding import ltd.evilcorp.atox.requireStringArg import ltd.evilcorp.atox.ui.BaseFragment import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.ConnectionStatus import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.android.x.viewmodel.viewModel class ContactProfileFragment : BaseFragment(FragmentContactProfileBinding::inflate) { - private val viewModel: ContactProfileViewModel by viewModels { vmFactory } + private val viewModel: ContactProfileViewModel by viewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) = binding.run { ViewCompat.setOnApplyWindowInsetsListener(view) { _, compat -> diff --git a/atox/src/main/kotlin/ui/contact_profile/ContactProfileViewModel.kt b/atox/src/main/kotlin/ui/contact_profile/ContactProfileViewModel.kt index c0a1d87d..0ab2d7a2 100644 --- a/atox/src/main/kotlin/ui/contact_profile/ContactProfileViewModel.kt +++ b/atox/src/main/kotlin/ui/contact_profile/ContactProfileViewModel.kt @@ -4,15 +4,22 @@ package ltd.evilcorp.atox.ui.contact_profile +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData -import javax.inject.Inject +import ltd.evilcorp.atox.App import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.domain.feature.ContactManager import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance + +class ContactProfileViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val contactManager: ContactManager by instance() -class ContactProfileViewModel @Inject constructor(contactManager: ContactManager) : ViewModel() { var publicKey: PublicKey = PublicKey("") val contact: LiveData by lazy { contactManager.get(publicKey).asLiveData() } } diff --git a/atox/src/main/kotlin/ui/contactlist/ContactListFragment.kt b/atox/src/main/kotlin/ui/contactlist/ContactListFragment.kt index 814245e6..252e091f 100644 --- a/atox/src/main/kotlin/ui/contactlist/ContactListFragment.kt +++ b/atox/src/main/kotlin/ui/contactlist/ContactListFragment.kt @@ -27,7 +27,6 @@ import androidx.core.view.GravityCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.google.android.material.navigation.NavigationView import ltd.evilcorp.atox.R @@ -41,13 +40,13 @@ import ltd.evilcorp.atox.ui.ReceiveShareDialog import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY import ltd.evilcorp.atox.ui.colorFromStatus import ltd.evilcorp.atox.ui.friend_request.FRIEND_REQUEST_PUBLIC_KEY -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.ConnectionStatus import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.core.vo.FriendRequest import ltd.evilcorp.core.vo.User import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.ToxSaveStatus +import org.kodein.di.android.x.viewmodel.viewModel const val ARG_SHARE = "share" private const val MAX_CONFIRM_DELETE_STRING_LENGTH = 32 @@ -59,7 +58,7 @@ class ContactListFragment : BaseFragment(FragmentContactListBinding::inflate), NavigationView.OnNavigationItemSelectedListener { - private val viewModel: ContactListViewModel by viewModels { vmFactory } + private val viewModel: ContactListViewModel by viewModel() private var _navHeader: NavHeaderContactListBinding? = null private val navHeader get() = _navHeader!! diff --git a/atox/src/main/kotlin/ui/contactlist/ContactListViewModel.kt b/atox/src/main/kotlin/ui/contactlist/ContactListViewModel.kt index 61e1f24f..daf69664 100644 --- a/atox/src/main/kotlin/ui/contactlist/ContactListViewModel.kt +++ b/atox/src/main/kotlin/ui/contactlist/ContactListViewModel.kt @@ -8,16 +8,16 @@ import android.content.ContentResolver import android.content.Context import android.net.Uri import android.widget.Toast +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import java.io.FileInputStream import java.io.FileOutputStream -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import ltd.evilcorp.atox.App import ltd.evilcorp.atox.R import ltd.evilcorp.atox.settings.Settings import ltd.evilcorp.atox.tox.ToxStarter @@ -37,22 +37,27 @@ import ltd.evilcorp.domain.tox.SaveOptions import ltd.evilcorp.domain.tox.Tox import ltd.evilcorp.domain.tox.ToxSaveStatus import ltd.evilcorp.domain.tox.testToxSave +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance + +class ContactListViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val scope: CoroutineScope by instance() + private val context: Context by instance() + private val resolver: ContentResolver by instance() + private val callManager: CallManager by instance() + private val chatManager: ChatManager by instance() + private val contactManager: ContactManager by instance() + private val fileTransferManager: FileTransferManager by instance() + private val friendRequestManager: FriendRequestManager by instance() + private val userManager: UserManager by instance() + private val notificationHelper: NotificationHelper by instance() + private val tox: Tox by instance() + private val toxStarter: ToxStarter by instance() + private val settings: Settings by instance() -class ContactListViewModel @Inject constructor( - private val scope: CoroutineScope, - private val context: Context, - private val resolver: ContentResolver, - private val callManager: CallManager, - private val chatManager: ChatManager, - private val contactManager: ContactManager, - private val fileTransferManager: FileTransferManager, - private val friendRequestManager: FriendRequestManager, - private val notificationHelper: NotificationHelper, - private val tox: Tox, - private val toxStarter: ToxStarter, - private val settings: Settings, - userManager: UserManager -) : ViewModel() { val publicKey by lazy { tox.publicKey } val user: LiveData by lazy { userManager.get(publicKey).asLiveData() } diff --git a/atox/src/main/kotlin/ui/create_profile/CreateProfileFragment.kt b/atox/src/main/kotlin/ui/create_profile/CreateProfileFragment.kt index 1ca28d07..214a15cb 100644 --- a/atox/src/main/kotlin/ui/create_profile/CreateProfileFragment.kt +++ b/atox/src/main/kotlin/ui/create_profile/CreateProfileFragment.kt @@ -17,17 +17,16 @@ import androidx.appcompat.app.AlertDialog import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import ltd.evilcorp.atox.R import ltd.evilcorp.atox.databinding.FragmentProfileBinding import ltd.evilcorp.atox.ui.BaseFragment -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.User import ltd.evilcorp.domain.tox.ToxSaveStatus +import org.kodein.di.android.x.viewmodel.viewModel class CreateProfileFragment : BaseFragment(FragmentProfileBinding::inflate) { - private val viewModel: CreateProfileViewModel by viewModels { vmFactory } + private val viewModel: CreateProfileViewModel by viewModel() private val importLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> if (uri == null) return@registerForActivityResult diff --git a/atox/src/main/kotlin/ui/create_profile/CreateProfileViewModel.kt b/atox/src/main/kotlin/ui/create_profile/CreateProfileViewModel.kt index 16a204f0..1da48561 100644 --- a/atox/src/main/kotlin/ui/create_profile/CreateProfileViewModel.kt +++ b/atox/src/main/kotlin/ui/create_profile/CreateProfileViewModel.kt @@ -6,21 +6,26 @@ package ltd.evilcorp.atox.ui.create_profile import android.content.ContentResolver import android.net.Uri -import androidx.lifecycle.ViewModel -import javax.inject.Inject +import androidx.lifecycle.AndroidViewModel +import ltd.evilcorp.atox.App import ltd.evilcorp.atox.tox.ToxStarter import ltd.evilcorp.core.vo.User import ltd.evilcorp.domain.feature.UserManager import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox import ltd.evilcorp.domain.tox.ToxSaveStatus +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance + +class CreateProfileViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val resolver: ContentResolver by instance() + private val userManager: UserManager by instance() + private val tox: Tox by instance() + private val toxStarter: ToxStarter by instance() -class CreateProfileViewModel @Inject constructor( - private val resolver: ContentResolver, - private val userManager: UserManager, - private val tox: Tox, - private val toxStarter: ToxStarter -) : ViewModel() { val publicKey: PublicKey by lazy { tox.publicKey } fun startTox(save: ByteArray? = null, password: String? = null): ToxSaveStatus = toxStarter.startTox(save, password) diff --git a/atox/src/main/kotlin/ui/friend_request/FriendRequestFragment.kt b/atox/src/main/kotlin/ui/friend_request/FriendRequestFragment.kt index 3f4a8304..990a698b 100644 --- a/atox/src/main/kotlin/ui/friend_request/FriendRequestFragment.kt +++ b/atox/src/main/kotlin/ui/friend_request/FriendRequestFragment.kt @@ -9,20 +9,19 @@ import android.view.View import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import ltd.evilcorp.atox.R import ltd.evilcorp.atox.databinding.FragmentFriendRequestBinding import ltd.evilcorp.atox.requireStringArg import ltd.evilcorp.atox.ui.BaseFragment -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.FriendRequest import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.android.x.viewmodel.viewModel const val FRIEND_REQUEST_PUBLIC_KEY = "FRIEND_REQUEST_PUBLIC_KEY" class FriendRequestFragment : BaseFragment(FragmentFriendRequestBinding::inflate) { - private val vm: FriendRequestViewModel by viewModels { vmFactory } + private val vm: FriendRequestViewModel by viewModel() private lateinit var friendRequest: FriendRequest override fun onViewCreated(view: View, savedInstanceState: Bundle?) = binding.run { diff --git a/atox/src/main/kotlin/ui/friend_request/FriendRequestViewModel.kt b/atox/src/main/kotlin/ui/friend_request/FriendRequestViewModel.kt index 28e3a9d4..a41c4971 100644 --- a/atox/src/main/kotlin/ui/friend_request/FriendRequestViewModel.kt +++ b/atox/src/main/kotlin/ui/friend_request/FriendRequestViewModel.kt @@ -4,17 +4,22 @@ package ltd.evilcorp.atox.ui.friend_request +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData -import javax.inject.Inject +import ltd.evilcorp.atox.App import ltd.evilcorp.core.vo.FriendRequest import ltd.evilcorp.domain.feature.FriendRequestManager import ltd.evilcorp.domain.tox.PublicKey +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance + +class FriendRequestViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val friendRequests: FriendRequestManager by instance() -class FriendRequestViewModel @Inject constructor( - private val friendRequests: FriendRequestManager -) : ViewModel() { fun byId(pk: PublicKey): LiveData = friendRequests.get(pk).asLiveData() fun accept(request: FriendRequest) = friendRequests.accept(request) fun reject(request: FriendRequest) = friendRequests.reject(request) diff --git a/atox/src/main/kotlin/ui/settings/SettingsFragment.kt b/atox/src/main/kotlin/ui/settings/SettingsFragment.kt index 5e3607c8..f551cd2b 100644 --- a/atox/src/main/kotlin/ui/settings/SettingsFragment.kt +++ b/atox/src/main/kotlin/ui/settings/SettingsFragment.kt @@ -19,9 +19,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.updatePadding import androidx.core.widget.doAfterTextChanged -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController -import java.lang.NumberFormatException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -32,8 +30,8 @@ import ltd.evilcorp.atox.databinding.FragmentSettingsBinding import ltd.evilcorp.atox.settings.BootstrapNodeSource import ltd.evilcorp.atox.settings.FtAutoAccept import ltd.evilcorp.atox.ui.BaseFragment -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.domain.tox.ProxyType +import org.kodein.di.android.x.viewmodel.viewModel private fun Spinner.onItemSelectedListener(callback: (Int) -> Unit) { this.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { @@ -45,7 +43,7 @@ private fun Spinner.onItemSelectedListener(callback: (Int) -> Unit) { } class SettingsFragment : BaseFragment(FragmentSettingsBinding::inflate) { - private val vm: SettingsViewModel by viewModels { vmFactory } + private val vm: SettingsViewModel by viewModel() private val scope = CoroutineScope(Dispatchers.Default) private val blockBackCallback = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() { diff --git a/atox/src/main/kotlin/ui/settings/SettingsViewModel.kt b/atox/src/main/kotlin/ui/settings/SettingsViewModel.kt index f29aa990..ceaedde1 100644 --- a/atox/src/main/kotlin/ui/settings/SettingsViewModel.kt +++ b/atox/src/main/kotlin/ui/settings/SettingsViewModel.kt @@ -5,21 +5,20 @@ package ltd.evilcorp.atox.ui.settings import android.content.ContentResolver -import android.content.Context import android.net.Uri import androidx.appcompat.app.AppCompatDelegate +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import java.io.File -import javax.inject.Inject import kotlin.math.max import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import ltd.evilcorp.atox.App import ltd.evilcorp.atox.settings.BootstrapNodeSource import ltd.evilcorp.atox.settings.FtAutoAccept import ltd.evilcorp.atox.settings.Settings @@ -31,6 +30,9 @@ import ltd.evilcorp.domain.tox.SaveOptions import ltd.evilcorp.domain.tox.Tox import ltd.evilcorp.domain.tox.ToxSaveStatus import ltd.evilcorp.domain.tox.testToxSave +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance private const val TOX_SHUTDOWN_POLL_DELAY_MS = 200L @@ -42,15 +44,16 @@ enum class ProxyStatus { NotFound, } -class SettingsViewModel @Inject constructor( - private val context: Context, - private val resolver: ContentResolver, - private val settings: Settings, - private val toxStarter: ToxStarter, - private val tox: Tox, - private val nodeParser: BootstrapNodeJsonParser, - private val nodeRegistry: BootstrapNodeRegistry, -) : ViewModel() { +class SettingsViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val filesDir: File by instance(tag = "files") + private val resolver: ContentResolver by instance() + private val settings: Settings by instance() + private val toxStarter: ToxStarter by instance() + private val tox: Tox by instance() + private val nodeRegistry: BootstrapNodeRegistry by instance() + private var restartNeeded = false private val _proxyStatus = MutableLiveData() @@ -197,7 +200,7 @@ class SettingsViewModel @Inject constructor( it.readBytes() } ?: return@withContext false - return@withContext nodeParser.parse(bytes.decodeToString()).isNotEmpty() + return@withContext BootstrapNodeJsonParser.parse(bytes.decodeToString()).isNotEmpty() } suspend fun importNodeJson(uri: Uri): Boolean = withContext(Dispatchers.IO) { @@ -205,7 +208,7 @@ class SettingsViewModel @Inject constructor( it.readBytes() } ?: return@withContext false - val out = File(context.filesDir, "user_nodes.json") + val out = File(filesDir, "user_nodes.json") out.delete() if (!out.createNewFile()) return@withContext false diff --git a/atox/src/main/kotlin/ui/user_profile/UserProfileFragment.kt b/atox/src/main/kotlin/ui/user_profile/UserProfileFragment.kt index c34cb80a..ba843cd7 100644 --- a/atox/src/main/kotlin/ui/user_profile/UserProfileFragment.kt +++ b/atox/src/main/kotlin/ui/user_profile/UserProfileFragment.kt @@ -28,7 +28,6 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.setPadding import androidx.core.view.updatePadding -import androidx.fragment.app.viewModels import androidx.lifecycle.viewModelScope import io.nayuki.qrcodegen.QrCode import java.io.File @@ -45,9 +44,9 @@ import ltd.evilcorp.atox.ui.Dp import ltd.evilcorp.atox.ui.Px import ltd.evilcorp.atox.ui.StatusDialog import ltd.evilcorp.atox.ui.colorFromStatus -import ltd.evilcorp.atox.vmFactory import ltd.evilcorp.core.vo.UserStatus import ltd.evilcorp.domain.tox.ToxID +import org.kodein.di.android.x.viewmodel.viewModel private const val TOX_MAX_NAME_LENGTH = 128 private const val TOX_MAX_STATUS_MESSAGE_LENGTH = 1007 @@ -58,7 +57,7 @@ private val qrCodeSharedImageSize = Px(1024) private val qrCodeSharedImagePadding = Px(200) class UserProfileFragment : BaseFragment(FragmentUserProfileBinding::inflate) { - private val vm: UserProfileViewModel by viewModels { vmFactory } + private val vm: UserProfileViewModel by viewModel() private lateinit var currentStatus: UserStatus override fun onViewCreated(view: View, savedInstanceState: Bundle?) = binding.run { diff --git a/atox/src/main/kotlin/ui/user_profile/UserProfileViewModel.kt b/atox/src/main/kotlin/ui/user_profile/UserProfileViewModel.kt index a9b5664f..3f2cbcbc 100644 --- a/atox/src/main/kotlin/ui/user_profile/UserProfileViewModel.kt +++ b/atox/src/main/kotlin/ui/user_profile/UserProfileViewModel.kt @@ -4,19 +4,24 @@ package ltd.evilcorp.atox.ui.user_profile +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData -import javax.inject.Inject +import ltd.evilcorp.atox.App import ltd.evilcorp.core.vo.User import ltd.evilcorp.core.vo.UserStatus import ltd.evilcorp.domain.feature.UserManager import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DIAware +import org.kodein.di.android.x.closestDI +import org.kodein.di.instance + +class UserProfileViewModel(app: App) : AndroidViewModel(app), DIAware { + override val di by closestDI() + + private val userManager: UserManager by instance() + private val tox: Tox by instance() -class UserProfileViewModel @Inject constructor( - private val userManager: UserManager, - private val tox: Tox -) : ViewModel() { val publicKey by lazy { tox.publicKey } val toxId by lazy { tox.toxId } val user: LiveData = userManager.get(publicKey).asLiveData() diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 96cc9a7a..bcfecad2 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -55,6 +55,8 @@ dependencies { api(libs.androidx.room.ktx) kapt(libs.androidx.room.compiler) + implementation(libs.kodein.di) + testImplementation(libs.test.junit.core) androidTestImplementation(libs.test.runner) diff --git a/core/src/main/kotlin/repository/ContactRepository.kt b/core/src/main/kotlin/repository/ContactRepository.kt index 65e66d62..1951252a 100644 --- a/core/src/main/kotlin/repository/ContactRepository.kt +++ b/core/src/main/kotlin/repository/ContactRepository.kt @@ -4,18 +4,18 @@ package ltd.evilcorp.core.repository -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import ltd.evilcorp.core.db.ContactDao import ltd.evilcorp.core.vo.ConnectionStatus import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.core.vo.UserStatus +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class ContactRepository(override val di: DI) : DIAware { + private val dao: ContactDao by instance() -@Singleton -class ContactRepository @Inject internal constructor( - private val dao: ContactDao -) { fun exists(publicKey: String): Boolean = dao.exists(publicKey) fun add(contact: Contact) = dao.save(contact) fun update(contact: Contact) = dao.update(contact) diff --git a/core/src/main/kotlin/repository/FileTransferRepository.kt b/core/src/main/kotlin/repository/FileTransferRepository.kt index 3df5af0c..2207c50f 100644 --- a/core/src/main/kotlin/repository/FileTransferRepository.kt +++ b/core/src/main/kotlin/repository/FileTransferRepository.kt @@ -4,16 +4,16 @@ package ltd.evilcorp.core.repository -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import ltd.evilcorp.core.db.FileTransferDao import ltd.evilcorp.core.vo.FileTransfer +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class FileTransferRepository(override val di: DI) : DIAware { + private val dao: FileTransferDao by instance() -@Singleton -class FileTransferRepository @Inject internal constructor( - private val dao: FileTransferDao -) { fun add(ft: FileTransfer): Long = dao.save(ft) diff --git a/core/src/main/kotlin/repository/FriendRequestRepository.kt b/core/src/main/kotlin/repository/FriendRequestRepository.kt index 23fb9b31..2f42bb07 100644 --- a/core/src/main/kotlin/repository/FriendRequestRepository.kt +++ b/core/src/main/kotlin/repository/FriendRequestRepository.kt @@ -4,16 +4,16 @@ package ltd.evilcorp.core.repository -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import ltd.evilcorp.core.db.FriendRequestDao import ltd.evilcorp.core.vo.FriendRequest +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class FriendRequestRepository(override val di: DI) : DIAware { + private val friendRequestDao: FriendRequestDao by instance() -@Singleton -class FriendRequestRepository @Inject internal constructor( - private val friendRequestDao: FriendRequestDao -) { fun add(friendRequest: FriendRequest) = friendRequestDao.save(friendRequest) diff --git a/core/src/main/kotlin/repository/MessageRepository.kt b/core/src/main/kotlin/repository/MessageRepository.kt index 9c1b437d..8194c5ea 100644 --- a/core/src/main/kotlin/repository/MessageRepository.kt +++ b/core/src/main/kotlin/repository/MessageRepository.kt @@ -5,17 +5,17 @@ package ltd.evilcorp.core.repository import java.util.Date -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import ltd.evilcorp.core.db.MessageDao import ltd.evilcorp.core.vo.Message +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class MessageRepository(override val di: DI) : DIAware { + private val messageDao: MessageDao by instance() + private val contactRepository: ContactRepository by instance() -@Singleton -class MessageRepository @Inject internal constructor( - private val messageDao: MessageDao, - private val contactRepository: ContactRepository, -) { fun add(message: Message) { messageDao.save(message) contactRepository.setLastMessage(message.publicKey, Date().time) diff --git a/core/src/main/kotlin/repository/UserRepository.kt b/core/src/main/kotlin/repository/UserRepository.kt index 710ca370..8510513a 100644 --- a/core/src/main/kotlin/repository/UserRepository.kt +++ b/core/src/main/kotlin/repository/UserRepository.kt @@ -4,18 +4,18 @@ package ltd.evilcorp.core.repository -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import ltd.evilcorp.core.db.UserDao import ltd.evilcorp.core.vo.ConnectionStatus import ltd.evilcorp.core.vo.User import ltd.evilcorp.core.vo.UserStatus +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class UserRepository(override val di: DI) : DIAware { + private val userDao: UserDao by instance() -@Singleton -class UserRepository @Inject internal constructor( - private val userDao: UserDao -) { fun exists(publicKey: String): Boolean = userDao.exists(publicKey) diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 7a0c3be1..05599020 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.javax.inject) implementation(libs.kotlinx.coroutines.core) + implementation(libs.kodein.di) api(libs.tox4j.api) implementation(libs.tox4j.c) diff --git a/domain/src/main/kotlin/feature/CallManager.kt b/domain/src/main/kotlin/feature/CallManager.kt index 20135b99..850b8689 100644 --- a/domain/src/main/kotlin/feature/CallManager.kt +++ b/domain/src/main/kotlin/feature/CallManager.kt @@ -4,14 +4,10 @@ package ltd.evilcorp.domain.feature -import android.content.Context import android.media.AudioManager import android.os.SystemClock import android.util.Log -import androidx.core.content.ContextCompat import im.tox.tox4j.av.exceptions.ToxavCallControlException -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -21,6 +17,10 @@ import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.domain.av.AudioCapture import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.DIContext +import org.kodein.di.instance sealed class CallState { object NotInCall : CallState() @@ -33,12 +33,11 @@ private const val AUDIO_CHANNELS = 1 private const val AUDIO_SAMPLING_RATE_HZ = 48_000 private const val AUDIO_SEND_INTERVAL_MS = 20 -@Singleton -class CallManager @Inject constructor( - private val tox: Tox, - private val scope: CoroutineScope, - context: Context, -) { +class CallManager(override val di: DI, override val diContext: DIContext<*>) : DIAware { + private val tox: Tox by instance() + private val scope: CoroutineScope by instance() + private val audioManager: AudioManager by instance() + private val _inCall = MutableStateFlow(CallState.NotInCall) val inCall: StateFlow get() = _inCall @@ -48,8 +47,6 @@ class CallManager @Inject constructor( private val _sendingAudio = MutableStateFlow(false) val sendingAudio: StateFlow get() = _sendingAudio - private val audioManager = ContextCompat.getSystemService(context, AudioManager::class.java) - fun addPendingCall(from: Contact) { val calls = mutableSetOf().apply { addAll(_pendingCalls.value) } calls.addAll(_pendingCalls.value) diff --git a/domain/src/main/kotlin/feature/ChatManager.kt b/domain/src/main/kotlin/feature/ChatManager.kt index cbd96634..2fc8fe78 100644 --- a/domain/src/main/kotlin/feature/ChatManager.kt +++ b/domain/src/main/kotlin/feature/ChatManager.kt @@ -7,8 +7,6 @@ package ltd.evilcorp.domain.feature import java.nio.ByteBuffer import java.nio.CharBuffer import java.nio.charset.StandardCharsets -import javax.inject.Inject -import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -21,6 +19,9 @@ import ltd.evilcorp.core.vo.Sender import ltd.evilcorp.domain.tox.MAX_MESSAGE_LENGTH import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance private fun String.chunked(chunkSizeInBytes: Int): MutableList { val encoder = StandardCharsets.UTF_8.newEncoder() @@ -40,13 +41,12 @@ private fun String.chunked(chunkSizeInBytes: Int): MutableList { return chunks } -@Singleton -class ChatManager @Inject constructor( - private val scope: CoroutineScope, - private val contactRepository: ContactRepository, - private val messageRepository: MessageRepository, - private val tox: Tox -) { +class ChatManager(override val di: DI) : DIAware { + private val scope: CoroutineScope by instance() + private val contactRepository: ContactRepository by instance() + private val messageRepository: MessageRepository by instance() + private val tox: Tox by instance() + var activeChat = "" set(value) { field = value diff --git a/domain/src/main/kotlin/feature/ContactManager.kt b/domain/src/main/kotlin/feature/ContactManager.kt index ba0f58cc..7ed0197a 100644 --- a/domain/src/main/kotlin/feature/ContactManager.kt +++ b/domain/src/main/kotlin/feature/ContactManager.kt @@ -4,7 +4,6 @@ package ltd.evilcorp.domain.feature -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import ltd.evilcorp.core.repository.ContactRepository @@ -12,12 +11,15 @@ import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox import ltd.evilcorp.domain.tox.ToxID +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class ContactManager(override val di: DI) : DIAware { + private val scope: CoroutineScope by instance() + private val contactRepository: ContactRepository by instance() + private val tox: Tox by instance() -class ContactManager @Inject constructor( - private val scope: CoroutineScope, - private val contactRepository: ContactRepository, - private val tox: Tox -) { fun get(publicKey: PublicKey) = contactRepository.get(publicKey.string()) fun getAll() = contactRepository.getAll() diff --git a/domain/src/main/kotlin/feature/FileTransferManager.kt b/domain/src/main/kotlin/feature/FileTransferManager.kt index 90185bc4..9a4504df 100644 --- a/domain/src/main/kotlin/feature/FileTransferManager.kt +++ b/domain/src/main/kotlin/feature/FileTransferManager.kt @@ -5,7 +5,6 @@ package ltd.evilcorp.domain.feature import android.content.ContentResolver -import android.content.Context import android.content.Intent import android.net.Uri import android.provider.OpenableColumns @@ -14,11 +13,8 @@ import im.tox.tox4j.core.enums.ToxFileControl import java.io.File import java.io.RandomAccessFile import java.util.Date -import javax.inject.Inject -import javax.inject.Singleton import kotlin.random.Random import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import ltd.evilcorp.core.repository.ContactRepository @@ -37,6 +33,10 @@ import ltd.evilcorp.core.vo.isStarted import ltd.evilcorp.domain.tox.MAX_AVATAR_SIZE import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.DIContext +import org.kodein.di.instance private const val TAG = "FileTransferManager" @@ -44,21 +44,21 @@ private const val TAG = "FileTransferManager" private const val FINGERPRINT_LEN = 8 private fun String.fingerprint() = take(FINGERPRINT_LEN) -@Singleton -class FileTransferManager @Inject constructor( - private val scope: CoroutineScope, - private val context: Context, - private val resolver: ContentResolver, - private val contactRepository: ContactRepository, - private val messageRepository: MessageRepository, - private val fileTransferRepository: FileTransferRepository, - private val tox: Tox -) { +class FileTransferManager(override val di: DI, override val diContext: DIContext<*>) : DIAware { + private val scope: CoroutineScope by instance() + private val contentResolver: ContentResolver by instance() + private val filesDir: File by instance(tag = "files") + private val resolver: ContentResolver by instance() + private val contactRepository: ContactRepository by instance() + private val messageRepository: MessageRepository by instance() + private val fileTransferRepository: FileTransferRepository by instance() + private val tox: Tox by instance() + private val fileTransfers: MutableList = mutableListOf() init { - File(context.filesDir, "ft").mkdir() - File(context.filesDir, "avatar").mkdir() + File(filesDir, "ft").mkdir() + File(filesDir, "avatar").mkdir() resolver.persistedUriPermissions.forEach { Log.w(TAG, "Clearing leftover permission for ${it.uri}") releaseFilePermission(it.uri) @@ -217,7 +217,7 @@ class FileTransferManager @Inject constructor( fun transfersFor(publicKey: PublicKey) = fileTransferRepository.get(publicKey.string()) fun create(pk: PublicKey, file: Uri) { - val (name, size) = context.contentResolver.query(file, null, null, null, null, null)?.use { cursor -> + val (name, size) = contentResolver.query(file, null, null, null, null, null)?.use { cursor -> cursor.moveToFirst() val fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)) val name = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) @@ -320,8 +320,8 @@ class FileTransferManager @Inject constructor( } private fun makeDestination(ft: FileTransfer) = - Uri.fromFile(File(File(File(context.filesDir, "ft"), ft.publicKey.fingerprint()), Random.nextLong().toString())) + Uri.fromFile(File(File(File(filesDir, "ft"), ft.publicKey.fingerprint()), Random.nextLong().toString())) - private fun wipAvatar(name: String): File = File(File(context.filesDir, "avatar"), "$name.wip") - private fun avatar(name: String): File = File(File(context.filesDir, "avatar"), name) + private fun wipAvatar(name: String): File = File(File(filesDir, "avatar"), "$name.wip") + private fun avatar(name: String): File = File(File(filesDir, "avatar"), name) } diff --git a/domain/src/main/kotlin/feature/FriendRequestManager.kt b/domain/src/main/kotlin/feature/FriendRequestManager.kt index 16f2daf9..c68e88e2 100644 --- a/domain/src/main/kotlin/feature/FriendRequestManager.kt +++ b/domain/src/main/kotlin/feature/FriendRequestManager.kt @@ -4,7 +4,6 @@ package ltd.evilcorp.domain.feature -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch @@ -14,13 +13,16 @@ import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.core.vo.FriendRequest import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class FriendRequestManager(override val di: DI) : DIAware { + private val scope: CoroutineScope by instance() + private val contactRepository: ContactRepository by instance() + private val friendRequestRepository: FriendRequestRepository by instance() + private val tox: Tox by instance() -class FriendRequestManager @Inject constructor( - private val scope: CoroutineScope, - private val contactRepository: ContactRepository, - private val friendRequestRepository: FriendRequestRepository, - private val tox: Tox -) { fun getAll(): Flow> = friendRequestRepository.getAll() fun get(id: PublicKey): Flow = friendRequestRepository.get(id.string()) diff --git a/domain/src/main/kotlin/feature/UserManager.kt b/domain/src/main/kotlin/feature/UserManager.kt index dd4d2636..14f1e437 100644 --- a/domain/src/main/kotlin/feature/UserManager.kt +++ b/domain/src/main/kotlin/feature/UserManager.kt @@ -4,7 +4,6 @@ package ltd.evilcorp.domain.feature -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import ltd.evilcorp.core.repository.UserRepository @@ -12,12 +11,15 @@ import ltd.evilcorp.core.vo.User import ltd.evilcorp.core.vo.UserStatus import ltd.evilcorp.domain.tox.PublicKey import ltd.evilcorp.domain.tox.Tox +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance + +class UserManager(override val di: DI) : DIAware { + private val scope: CoroutineScope by instance() + private val userRepository: UserRepository by instance() + private val tox: Tox by instance() -class UserManager @Inject constructor( - private val scope: CoroutineScope, - private val userRepository: UserRepository, - private val tox: Tox -) { fun get(publicKey: PublicKey) = userRepository.get(publicKey.string()) fun create(user: User) = scope.launch { diff --git a/domain/src/main/kotlin/tox/BootstrapNodeJsonParser.kt b/domain/src/main/kotlin/tox/BootstrapNodeJsonParser.kt index 4b741bdd..adb82d2c 100644 --- a/domain/src/main/kotlin/tox/BootstrapNodeJsonParser.kt +++ b/domain/src/main/kotlin/tox/BootstrapNodeJsonParser.kt @@ -5,13 +5,12 @@ package ltd.evilcorp.domain.tox import android.util.Log -import javax.inject.Inject import org.json.JSONObject private const val TAG = "BootstrapNodeJsonParser" // Parses a json string containing json formatted the way it is on https://nodes.tox.chat/json -class BootstrapNodeJsonParser @Inject constructor() { +object BootstrapNodeJsonParser { fun parse(jsonString: String): List = try { val nodes = mutableListOf() diff --git a/domain/src/main/kotlin/tox/SaveManager.kt b/domain/src/main/kotlin/tox/SaveManager.kt index 028e811d..0c6a1d6a 100644 --- a/domain/src/main/kotlin/tox/SaveManager.kt +++ b/domain/src/main/kotlin/tox/SaveManager.kt @@ -4,43 +4,8 @@ package ltd.evilcorp.domain.tox -import android.content.Context -import android.util.Log -import java.io.File -import javax.inject.Inject - -private const val TAG = "SaveManager" - -class SaveManager @Inject constructor(val context: Context) { - private val saveDir = context.filesDir - - init { - if (!saveDir.exists()) { - saveDir.mkdir() - } - } - - fun list(): List = saveDir.listFiles()?.let { saves -> - saves.filter { it.extension == "tox" }.map { it.nameWithoutExtension } - } ?: listOf() - - fun save(publicKey: PublicKey, saveData: ByteArray) = File("$saveDir/${publicKey.string()}.tox").run { - if (!exists()) { - createNewFile() - } - - Log.i(TAG, "Saving profile to $this") - writeBytes(saveData) - } - - fun load(publicKey: PublicKey): ByteArray? = tryReadBytes(File(pathTo(publicKey))) - - private fun tryReadBytes(saveFile: File): ByteArray? = - if (saveFile.exists()) { - saveFile.readBytes() - } else { - null - } - - private fun pathTo(publicKey: PublicKey) = "$saveDir/${publicKey.string()}.tox" +interface SaveManager { + fun list(): List + fun save(publicKey: PublicKey, saveData: ByteArray) + fun load(publicKey: PublicKey): ByteArray? } diff --git a/domain/src/main/kotlin/tox/SaveManagerImpl.kt b/domain/src/main/kotlin/tox/SaveManagerImpl.kt new file mode 100644 index 00000000..11739baa --- /dev/null +++ b/domain/src/main/kotlin/tox/SaveManagerImpl.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2019 aTox contributors +// +// SPDX-License-Identifier: GPL-3.0-only + +package ltd.evilcorp.domain.tox + +import android.util.Log +import java.io.File +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.DIContext +import org.kodein.di.instance + +private const val TAG = "SaveManager" + +class SaveManagerImpl(override val di: DI, override val diContext: DIContext<*>) : SaveManager, DIAware { + private val saveDir: File by instance(tag = "files") + + init { + if (!saveDir.exists()) { + saveDir.mkdir() + } + } + + override fun list(): List = saveDir.listFiles()?.let { saves -> + saves.filter { it.extension == "tox" }.map { it.nameWithoutExtension } + } ?: listOf() + + override fun save(publicKey: PublicKey, saveData: ByteArray) = File("$saveDir/${publicKey.string()}.tox").run { + if (!exists()) { + createNewFile() + } + + Log.i(TAG, "Saving profile to $this") + writeBytes(saveData) + } + + override fun load(publicKey: PublicKey): ByteArray? = tryReadBytes(File(pathTo(publicKey))) + + private fun tryReadBytes(saveFile: File): ByteArray? = + if (saveFile.exists()) { + saveFile.readBytes() + } else { + null + } + + private fun pathTo(publicKey: PublicKey) = "$saveDir/${publicKey.string()}.tox" +} diff --git a/domain/src/main/kotlin/tox/Tox.kt b/domain/src/main/kotlin/tox/Tox.kt index c0348cc5..3f242b98 100644 --- a/domain/src/main/kotlin/tox/Tox.kt +++ b/domain/src/main/kotlin/tox/Tox.kt @@ -8,8 +8,6 @@ import android.util.Log import im.tox.tox4j.core.exceptions.ToxBootstrapException import im.tox.tox4j.crypto.ToxCryptoConstants import im.tox.tox4j.impl.jni.ToxCryptoImpl -import javax.inject.Inject -import javax.inject.Singleton import kotlin.random.Random import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -23,17 +21,19 @@ import ltd.evilcorp.core.vo.Contact import ltd.evilcorp.core.vo.FileKind import ltd.evilcorp.core.vo.MessageType import ltd.evilcorp.core.vo.UserStatus +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance private const val TAG = "Tox" -@Singleton -class Tox @Inject constructor( - private val scope: CoroutineScope, - private val contactRepository: ContactRepository, - private val userRepository: UserRepository, - private val saveManager: SaveManager, - private val nodeRegistry: BootstrapNodeRegistry, -) { +class Tox(override val di: DI) : DIAware { + private val scope: CoroutineScope by instance() + private val contactRepository: ContactRepository by instance() + private val userRepository: UserRepository by instance() + private val saveManager: SaveManager by instance() + private val nodeRegistry: BootstrapNodeRegistry by instance() + val toxId: ToxID get() = tox.getToxId() val publicKey: PublicKey by lazy { tox.getPublicKey() } var nospam: Int @@ -64,18 +64,14 @@ class Tox @Inject constructor( private lateinit var tox: ToxWrapper - fun start(saveOption: SaveOptions, password: String?, listener: ToxEventListener, avListener: ToxAvEventListener) { + fun start(saveOption: SaveOptions, password: String?) { tox = if (password == null) { passkey = null - ToxWrapper(listener, avListener, saveOption) + ToxWrapper(saveOption) } else { val salt = ToxCryptoImpl.getSalt(saveOption.saveData) passkey = ToxCryptoImpl.passKeyDeriveWithSalt(password.toByteArray(), salt) - ToxWrapper( - listener, - avListener, - saveOption.copy(saveData = ToxCryptoImpl.decrypt(saveOption.saveData, passkey)), - ) + ToxWrapper(saveOption.copy(saveData = ToxCryptoImpl.decrypt(saveOption.saveData, passkey))) } this.password = password diff --git a/domain/src/main/kotlin/tox/ToxAvEventListener.kt b/domain/src/main/kotlin/tox/ToxAvEventListener.kt index 886e5f3d..5533417c 100644 --- a/domain/src/main/kotlin/tox/ToxAvEventListener.kt +++ b/domain/src/main/kotlin/tox/ToxAvEventListener.kt @@ -7,7 +7,6 @@ package ltd.evilcorp.domain.tox import im.tox.tox4j.av.callbacks.ToxAvEventListener import im.tox.tox4j.av.enums.ToxavFriendCallState import java.util.EnumSet -import javax.inject.Inject import scala.Option import scala.Tuple3 @@ -29,7 +28,7 @@ typealias VideoReceiveFrameHandler = ( typealias AudioReceiveFrameHandler = (pk: String, pcm: ShortArray, channels: Int, samplingRate: Int) -> Unit typealias AudioBitRateHandler = (pk: String, bitRate: Int) -> Unit -class ToxAvEventListener @Inject constructor() : ToxAvEventListener { +object ToxAvEventListener : ToxAvEventListener { var contactMapping: List> = listOf() var callHandler: CallHandler = { _, _, _ -> } diff --git a/domain/src/main/kotlin/tox/ToxEventListener.kt b/domain/src/main/kotlin/tox/ToxEventListener.kt index a78d5b5a..4750ddc1 100644 --- a/domain/src/main/kotlin/tox/ToxEventListener.kt +++ b/domain/src/main/kotlin/tox/ToxEventListener.kt @@ -9,7 +9,6 @@ import im.tox.tox4j.core.enums.ToxConnection import im.tox.tox4j.core.enums.ToxFileControl import im.tox.tox4j.core.enums.ToxMessageType import im.tox.tox4j.core.enums.ToxUserStatus -import javax.inject.Inject import ltd.evilcorp.core.vo.ConnectionStatus import ltd.evilcorp.core.vo.UserStatus @@ -34,7 +33,7 @@ typealias SelfConnectionStatusHandler = (status: ConnectionStatus) -> Unit typealias FriendTypingHandler = (publicKey: String, isTyping: Boolean) -> Unit typealias FileChunkRequestHandler = (publicKey: String, fileNo: Int, position: Long, length: Int) -> Unit -class ToxEventListener @Inject constructor() : ToxCoreEventListener { +object ToxEventListener : ToxCoreEventListener { var contactMapping: List> = listOf() var friendLosslessPacketHandler: FriendLosslessPacketHandler = { _, _ -> } diff --git a/domain/src/main/kotlin/tox/ToxWrapper.kt b/domain/src/main/kotlin/tox/ToxWrapper.kt index 8c14509a..65e6a244 100644 --- a/domain/src/main/kotlin/tox/ToxWrapper.kt +++ b/domain/src/main/kotlin/tox/ToxWrapper.kt @@ -33,11 +33,7 @@ enum class CustomPacketError { TooLong, } -class ToxWrapper( - private val eventListener: ToxEventListener, - private val avEventListener: ToxAvEventListener, - options: SaveOptions -) { +class ToxWrapper(options: SaveOptions) { private val tox: ToxCoreImpl = ToxCoreImpl( options.toToxOptions() @@ -51,8 +47,8 @@ class ToxWrapper( private fun updateContactMapping() { val contacts = getContacts() - eventListener.contactMapping = contacts - avEventListener.contactMapping = contacts + ToxEventListener.contactMapping = contacts + ToxAvEventListener.contactMapping = contacts } fun bootstrap(address: String, port: Int, publicKey: ByteArray) { @@ -65,8 +61,8 @@ class ToxWrapper( tox.close() } - fun iterate(): Unit = tox.iterate(eventListener, Unit) - fun iterateAv(): Unit = av.iterate(avEventListener, Unit) + fun iterate(): Unit = tox.iterate(ToxEventListener, Unit) + fun iterateAv(): Unit = av.iterate(ToxAvEventListener, Unit) fun iterationInterval(): Long = tox.iterationInterval().toLong() fun iterationIntervalAv(): Long = av.iterationInterval().toLong() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 66102ab0..20b124a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -sdk-min = "19" +sdk-min = "21" sdk-target = "31" kotlin = "1.6.20" @@ -9,6 +9,7 @@ androidx-test = "1.4.0" coroutines = "1.6.1" dagger = "2.41" espresso = "3.4.0" +kodein-di = "7.11.0" lifecycle = "2.4.1" navigation = "2.4.2" room = "2.4.2" @@ -51,6 +52,10 @@ google-dagger-core = { module = "com.google.dagger:dagger", version.ref = "dagge javax-inject = "javax.inject:javax.inject:1" +kodein-di = { module = "org.kodein.di:kodein-di", version.ref = "kodein-di" } +kodein-di-framework-android-x = { module = "org.kodein.di:kodein-di-framework-android-x", version.ref = "kodein-di" } +kodein-di-framework-android-x-viewmodel = { module = "org.kodein.di:kodein-di-framework-android-x-viewmodel", version.ref = "kodein-di" } + kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }