Cleanup dagger setup (#2300)

* cleanup dagger setup

* fix tests

* fix ktlint

* cleanup FragmentBuildersModule
This commit is contained in:
Konrad Pozniak 2022-01-23 20:24:55 +01:00 committed by GitHub
parent 0b70f52ad2
commit e29567c9ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 44 additions and 159 deletions

View file

@ -2,21 +2,19 @@ package com.keylesspalace.tusky.appstore
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.PublishSubject import io.reactivex.rxjava3.subjects.PublishSubject
import javax.inject.Inject
import javax.inject.Singleton
interface Event interface Event
interface Dispatchable : Event interface Dispatchable : Event
interface EventHub { @Singleton
val events: Observable<Event> class EventHub @Inject constructor() {
fun dispatch(event: Dispatchable)
}
object EventHubImpl : EventHub {
private val eventsSubject = PublishSubject.create<Event>() private val eventsSubject = PublishSubject.create<Event>()
override val events: Observable<Event> = eventsSubject val events: Observable<Event> = eventsSubject
override fun dispatch(event: Dispatchable) { fun dispatch(event: Dispatchable) {
eventsSubject.onNext(event) eventsSubject.onNext(event)
} }
} }

View file

@ -41,6 +41,7 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.util.Date import java.util.Date
import javax.inject.Inject
sealed class UploadEvent { sealed class UploadEvent {
data class ProgressEvent(val percentage: Int) : UploadEvent() data class ProgressEvent(val percentage: Int) : UploadEvent()
@ -61,21 +62,16 @@ fun createNewImageFile(context: Context): File {
data class PreparedMedia(val type: QueuedMedia.Type, val uri: Uri, val size: Long) data class PreparedMedia(val type: QueuedMedia.Type, val uri: Uri, val size: Long)
interface MediaUploader {
fun prepareMedia(inUri: Uri): Single<PreparedMedia>
fun uploadMedia(media: QueuedMedia): Observable<UploadEvent>
}
class AudioSizeException : Exception() class AudioSizeException : Exception()
class VideoSizeException : Exception() class VideoSizeException : Exception()
class MediaTypeException : Exception() class MediaTypeException : Exception()
class CouldNotOpenFileException : Exception() class CouldNotOpenFileException : Exception()
class MediaUploaderImpl( class MediaUploader @Inject constructor(
private val context: Context, private val context: Context,
private val mastodonApi: MastodonApi private val mastodonApi: MastodonApi
) : MediaUploader { ) {
override fun uploadMedia(media: QueuedMedia): Observable<UploadEvent> { fun uploadMedia(media: QueuedMedia): Observable<UploadEvent> {
return Observable return Observable
.fromCallable { .fromCallable {
if (shouldResizeMedia(media)) { if (shouldResizeMedia(media)) {
@ -86,7 +82,7 @@ class MediaUploaderImpl(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
} }
override fun prepareMedia(inUri: Uri): Single<PreparedMedia> { fun prepareMedia(inUri: Uri): Single<PreparedMedia> {
return Single.fromCallable { return Single.fromCallable {
var mediaSize = getMediaSize(contentResolver, inUri) var mediaSize = getMediaSize(contentResolver, inUri)
var uri = inUri var uri = inUri

View file

@ -1,5 +1,6 @@
package com.keylesspalace.tusky.components.notifications package com.keylesspalace.tusky.components.notifications
import android.content.Context
import android.util.Log import android.util.Log
import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AccountManager
@ -12,7 +13,7 @@ import javax.inject.Inject
class NotificationFetcher @Inject constructor( class NotificationFetcher @Inject constructor(
private val mastodonApi: MastodonApi, private val mastodonApi: MastodonApi,
private val accountManager: AccountManager, private val accountManager: AccountManager,
private val notifier: Notifier private val context: Context
) { ) {
fun fetchAndShow() { fun fetchAndShow() {
for (account in accountManager.getAllAccountsOrderedByActive()) { for (account in accountManager.getAllAccountsOrderedByActive()) {
@ -20,7 +21,7 @@ class NotificationFetcher @Inject constructor(
try { try {
val notifications = fetchNotifications(account) val notifications = fetchNotifications(account)
notifications.forEachIndexed { index, notification -> notifications.forEachIndexed { index, notification ->
notifier.show(notification, account, index == 0) NotificationHelper.make(context, notification, account, index == 0)
} }
accountManager.saveAccount(account) accountManager.saveAccount(account)
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -1,20 +0,0 @@
package com.keylesspalace.tusky.components.notifications
import android.content.Context
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.entity.Notification
/**
* Shows notifications.
*/
interface Notifier {
fun show(notification: Notification, account: AccountEntity, isFirstInBatch: Boolean)
}
class SystemNotifier(
private val context: Context
) : Notifier {
override fun show(notification: Notification, account: AccountEntity, isFirstInBatch: Boolean) {
NotificationHelper.make(context, notification, account, isFirstInBatch)
}
}

View file

@ -34,8 +34,7 @@ import javax.inject.Singleton
ActivitiesModule::class, ActivitiesModule::class,
ServicesModule::class, ServicesModule::class,
BroadcastReceiverModule::class, BroadcastReceiverModule::class,
ViewModelModule::class, ViewModelModule::class
MediaUploaderModule::class
] ]
) )
interface AppComponent { interface AppComponent {

View file

@ -18,19 +18,11 @@ package com.keylesspalace.tusky.di
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.room.Room import androidx.room.Room
import com.keylesspalace.tusky.TuskyApplication import com.keylesspalace.tusky.TuskyApplication
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.EventHubImpl
import com.keylesspalace.tusky.components.notifications.Notifier
import com.keylesspalace.tusky.components.notifications.SystemNotifier
import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.Converters import com.keylesspalace.tusky.db.Converters
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases
import com.keylesspalace.tusky.network.TimelineCasesImpl
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import javax.inject.Singleton import javax.inject.Singleton
@ -53,23 +45,6 @@ class AppModule {
return PreferenceManager.getDefaultSharedPreferences(app) return PreferenceManager.getDefaultSharedPreferences(app)
} }
@Provides
fun providesBroadcastManager(app: Application): LocalBroadcastManager {
return LocalBroadcastManager.getInstance(app)
}
@Provides
fun providesTimelineUseCases(
api: MastodonApi,
eventHub: EventHub
): TimelineCases {
return TimelineCasesImpl(api, eventHub)
}
@Provides
@Singleton
fun providesEventHub(): EventHub = EventHubImpl
@Provides @Provides
@Singleton @Singleton
fun providesDatabase(appContext: Context, converters: Converters): AppDatabase { fun providesDatabase(appContext: Context, converters: Converters): AppDatabase {
@ -90,8 +65,4 @@ class AppModule {
) )
.build() .build()
} }
@Provides
@Singleton
fun notifier(context: Context): Notifier = SystemNotifier(context)
} }

View file

@ -56,9 +56,6 @@ abstract class FragmentBuildersModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun notificationsFragment(): NotificationsFragment abstract fun notificationsFragment(): NotificationsFragment
@ContributesAndroidInjector
abstract fun searchFragment(): SearchStatusesFragment
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun notificationPreferencesFragment(): NotificationPreferencesFragment abstract fun notificationPreferencesFragment(): NotificationPreferencesFragment
@ -66,7 +63,7 @@ abstract class FragmentBuildersModule {
abstract fun accountPreferencesFragment(): AccountPreferencesFragment abstract fun accountPreferencesFragment(): AccountPreferencesFragment
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun directMessagesPreferencesFragment(): ConversationsFragment abstract fun conversationsFragment(): ConversationsFragment
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun accountInListsFragment(): AccountsInListFragment abstract fun accountInListsFragment(): AccountsInListFragment
@ -83,6 +80,9 @@ abstract class FragmentBuildersModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun instanceListFragment(): InstanceListFragment abstract fun instanceListFragment(): InstanceListFragment
@ContributesAndroidInjector
abstract fun searchStatusesFragment(): SearchStatusesFragment
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun searchAccountFragment(): SearchAccountsFragment abstract fun searchAccountFragment(): SearchAccountsFragment

View file

@ -1,30 +0,0 @@
/* Copyright 2019 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.di
import android.content.Context
import com.keylesspalace.tusky.components.compose.MediaUploader
import com.keylesspalace.tusky.components.compose.MediaUploaderImpl
import com.keylesspalace.tusky.network.MastodonApi
import dagger.Module
import dagger.Provides
@Module
class MediaUploaderModule {
@Provides
fun providesMediaUploder(context: Context, mastodonApi: MastodonApi): MediaUploader =
MediaUploaderImpl(context, mastodonApi)
}

View file

@ -15,25 +15,12 @@
package com.keylesspalace.tusky.di package com.keylesspalace.tusky.di
import android.content.Context
import com.keylesspalace.tusky.service.SendTootService import com.keylesspalace.tusky.service.SendTootService
import com.keylesspalace.tusky.service.ServiceClient
import com.keylesspalace.tusky.service.ServiceClientImpl
import dagger.Module import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
@Module @Module
abstract class ServicesModule { abstract class ServicesModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun contributesSendTootService(): SendTootService abstract fun contributesSendTootService(): SendTootService
@Module
companion object {
@Provides
@JvmStatic
fun providesServiceClient(context: Context): ServiceClient {
return ServiceClientImpl(context)
}
}
} }

View file

@ -32,27 +32,16 @@ import com.keylesspalace.tusky.entity.Status
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.addTo import io.reactivex.rxjava3.kotlin.addTo
import javax.inject.Inject
/** /**
* Created by charlag on 3/24/18. * Created by charlag on 3/24/18.
*/ */
interface TimelineCases { class TimelineCases @Inject constructor(
fun reblog(statusId: String, reblog: Boolean): Single<Status>
fun favourite(statusId: String, favourite: Boolean): Single<Status>
fun bookmark(statusId: String, bookmark: Boolean): Single<Status>
fun mute(statusId: String, notifications: Boolean, duration: Int?)
fun block(statusId: String)
fun delete(statusId: String): Single<DeletedStatus>
fun pin(statusId: String, pin: Boolean): Single<Status>
fun voteInPoll(statusId: String, pollId: String, choices: List<Int>): Single<Poll>
fun muteConversation(statusId: String, mute: Boolean): Single<Status>
}
class TimelineCasesImpl(
private val mastodonApi: MastodonApi, private val mastodonApi: MastodonApi,
private val eventHub: EventHub private val eventHub: EventHub
) : TimelineCases { ) {
/** /**
* Unused yet but can be use for cancellation later. It's always a good idea to save * Unused yet but can be use for cancellation later. It's always a good idea to save
@ -60,7 +49,7 @@ class TimelineCasesImpl(
*/ */
private val cancelDisposable = CompositeDisposable() private val cancelDisposable = CompositeDisposable()
override fun reblog(statusId: String, reblog: Boolean): Single<Status> { fun reblog(statusId: String, reblog: Boolean): Single<Status> {
val call = if (reblog) { val call = if (reblog) {
mastodonApi.reblogStatus(statusId) mastodonApi.reblogStatus(statusId)
} else { } else {
@ -71,7 +60,7 @@ class TimelineCasesImpl(
} }
} }
override fun favourite(statusId: String, favourite: Boolean): Single<Status> { fun favourite(statusId: String, favourite: Boolean): Single<Status> {
val call = if (favourite) { val call = if (favourite) {
mastodonApi.favouriteStatus(statusId) mastodonApi.favouriteStatus(statusId)
} else { } else {
@ -82,7 +71,7 @@ class TimelineCasesImpl(
} }
} }
override fun bookmark(statusId: String, bookmark: Boolean): Single<Status> { fun bookmark(statusId: String, bookmark: Boolean): Single<Status> {
val call = if (bookmark) { val call = if (bookmark) {
mastodonApi.bookmarkStatus(statusId) mastodonApi.bookmarkStatus(statusId)
} else { } else {
@ -93,7 +82,7 @@ class TimelineCasesImpl(
} }
} }
override fun muteConversation(statusId: String, mute: Boolean): Single<Status> { fun muteConversation(statusId: String, mute: Boolean): Single<Status> {
val call = if (mute) { val call = if (mute) {
mastodonApi.muteConversation(statusId) mastodonApi.muteConversation(statusId)
} else { } else {
@ -104,7 +93,7 @@ class TimelineCasesImpl(
} }
} }
override fun mute(statusId: String, notifications: Boolean, duration: Int?) { fun mute(statusId: String, notifications: Boolean, duration: Int?) {
mastodonApi.muteAccount(statusId, notifications, duration) mastodonApi.muteAccount(statusId, notifications, duration)
.subscribe( .subscribe(
{ {
@ -117,7 +106,7 @@ class TimelineCasesImpl(
.addTo(cancelDisposable) .addTo(cancelDisposable)
} }
override fun block(statusId: String) { fun block(statusId: String) {
mastodonApi.blockAccount(statusId) mastodonApi.blockAccount(statusId)
.subscribe( .subscribe(
{ {
@ -130,14 +119,14 @@ class TimelineCasesImpl(
.addTo(cancelDisposable) .addTo(cancelDisposable)
} }
override fun delete(statusId: String): Single<DeletedStatus> { fun delete(statusId: String): Single<DeletedStatus> {
return mastodonApi.deleteStatus(statusId) return mastodonApi.deleteStatus(statusId)
.doAfterSuccess { .doAfterSuccess {
eventHub.dispatch(StatusDeletedEvent(statusId)) eventHub.dispatch(StatusDeletedEvent(statusId))
} }
} }
override fun pin(statusId: String, pin: Boolean): Single<Status> { fun pin(statusId: String, pin: Boolean): Single<Status> {
// Replace with extension method if we use RxKotlin // Replace with extension method if we use RxKotlin
return (if (pin) mastodonApi.pinStatus(statusId) else mastodonApi.unpinStatus(statusId)) return (if (pin) mastodonApi.pinStatus(statusId) else mastodonApi.unpinStatus(statusId))
.doAfterSuccess { .doAfterSuccess {
@ -145,7 +134,7 @@ class TimelineCasesImpl(
} }
} }
override fun voteInPoll(statusId: String, pollId: String, choices: List<Int>): Single<Poll> { fun voteInPoll(statusId: String, pollId: String, choices: List<Int>): Single<Poll> {
if (choices.isEmpty()) { if (choices.isEmpty()) {
return Single.error(IllegalStateException()) return Single.error(IllegalStateException())
} }

View file

@ -16,19 +16,12 @@
package com.keylesspalace.tusky.service package com.keylesspalace.tusky.service
import android.content.Context import android.content.Context
import android.os.Build import androidx.core.content.ContextCompat
import javax.inject.Inject
interface ServiceClient { class ServiceClient @Inject constructor(private val context: Context) {
fun sendToot(tootToSend: TootToSend) fun sendToot(tootToSend: TootToSend) {
}
class ServiceClientImpl(private val context: Context) : ServiceClient {
override fun sendToot(tootToSend: TootToSend) {
val intent = SendTootService.sendTootIntent(context, tootToSend) val intent = SendTootService.sendTootIntent(context, tootToSend)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ContextCompat.startForegroundService(context, intent)
context.startForegroundService(intent)
} else {
context.startService(intent)
}
} }
} }

View file

@ -9,7 +9,7 @@ import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.google.gson.Gson import com.google.gson.Gson
import com.keylesspalace.tusky.appstore.EventHubImpl import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.components.timeline.TimelinePagingAdapter.Companion.TimelineDifferCallback import com.keylesspalace.tusky.components.timeline.TimelinePagingAdapter.Companion.TimelineDifferCallback
import com.keylesspalace.tusky.components.timeline.viewmodel.CachedTimelineViewModel import com.keylesspalace.tusky.components.timeline.viewmodel.CachedTimelineViewModel
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
@ -20,7 +20,7 @@ import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.Converters import com.keylesspalace.tusky.db.Converters
import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.FilterModel
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCasesImpl import com.keylesspalace.tusky.network.TimelineCases
import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
@ -66,6 +66,8 @@ class TimelineViewModelTest {
) )
} }
private val eventHub = EventHub()
private lateinit var db: AppDatabase private lateinit var db: AppDatabase
@Before @Before
@ -115,9 +117,9 @@ class TimelineViewModelTest {
} }
val viewModel = NetworkTimelineViewModel( val viewModel = NetworkTimelineViewModel(
TimelineCasesImpl(api, EventHubImpl), TimelineCases(api, eventHub),
api, api,
EventHubImpl, eventHub,
accountManager, accountManager,
mock(), mock(),
FilterModel() FilterModel()
@ -171,9 +173,9 @@ class TimelineViewModelTest {
} }
val viewModel = CachedTimelineViewModel( val viewModel = CachedTimelineViewModel(
TimelineCasesImpl(api, EventHubImpl), TimelineCases(api, eventHub),
api, api,
EventHubImpl, eventHub,
accountManager, accountManager,
mock(), mock(),
FilterModel(), FilterModel(),
@ -189,7 +191,6 @@ class TimelineViewModelTest {
workerDispatcher = testDispatcher workerDispatcher = testDispatcher
) )
var x = 1
viewModel.statuses.take(1000).collectLatest { viewModel.statuses.take(1000).collectLatest {
testScope.launch { testScope.launch {
differ.submitData(it) differ.submitData(it)