Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ android {
applicationId "com.masterwok.shrimplesearch"
minSdkVersion project.minSdkVersion
targetSdkVersion project.targetSdkVersion
versionCode 500053
versionName "2.1.6"
versionCode 500054
versionName "2.1.7"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -71,6 +71,9 @@ dependencies {
def lifecycle_version = '2.2.0'
def dagger_version = '2.28.1'

implementation 'com.google.android.play:core:1.10.0'
implementation 'com.google.android.play:core-ktx:1.8.1'

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ val DEFAULT_USER_SETTINGS = UserSettings(
isExitDialogEnabled = true
)

/**
* The amount of times a user must tap a result item before being presented with an in-app review
* workflow.
*/
const val IN_APP_REVIEW_RESULT_ITEM_TAP_COUNT = 6
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@ package com.masterwok.shrimplesearch.common.data.repositories

import com.masterwok.shrimplesearch.common.data.repositories.contracts.JackettService
import com.masterwok.shrimplesearch.common.data.repositories.contracts.UserSettingsRepository
import com.masterwok.shrimplesearch.common.utils.notNull
import com.masterwok.xamarininterface.enums.QueryState
import com.masterwok.xamarininterface.contracts.IJackettHarness
import com.masterwok.xamarininterface.contracts.IJackettHarnessListener
import com.masterwok.xamarininterface.enums.QueryState
import com.masterwok.xamarininterface.models.IndexerQueryResult
import com.masterwok.xamarininterface.models.Query
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.withContext
import java.lang.ref.WeakReference

class JackettServiceImpl constructor(
private val jackettHarness: IJackettHarness,
private val userSettingsRepository: UserSettingsRepository,
private val indexerBlockList: List<String>
) : JackettService {

private val jackettHarnessListener: IJackettHarnessListener = JackettHarnessListener(this)
private val jackettHarnessListener: IJackettHarnessListener = JackettHarnessListener()

private val listeners = mutableListOf<JackettService.Listener>()

Expand Down Expand Up @@ -78,29 +76,25 @@ class JackettServiceImpl constructor(
listeners.remove(listener)
}

private class JackettHarnessListener(jackettService: JackettServiceImpl) :
IJackettHarnessListener {

private val weakJackettService = WeakReference(jackettService)
private inner class JackettHarnessListener : IJackettHarnessListener {

override fun onIndexersInitialized() = weakJackettService.get().notNull { jackettService ->
jackettService.listeners.forEach { it.onIndexersInitialized() }
override fun onIndexersInitialized() = listeners.forEach {
it.onIndexersInitialized()
}

override fun onIndexerInitialized() = weakJackettService.get().notNull { jackettService ->
jackettService.listeners.forEach { it.onIndexerInitialized() }
override fun onIndexerInitialized() = listeners.forEach {
it.onIndexerInitialized()
}

override fun onResultsUpdated() = weakJackettService.get().notNull { jackettService ->
if (jackettService.queryState != QueryState.Aborted) {
jackettService.listeners.forEach { it.onResultsUpdated() }
override fun onResultsUpdated() {
if (queryState != QueryState.Aborted) {
listeners.forEach { it.onResultsUpdated() }
}
}

override fun onQueryStateChange(queryState: QueryState) =
weakJackettService.get().notNull { jackettService ->
jackettService.listeners.forEach { it.onQueryStateChange(queryState) }
}
override fun onQueryStateChange(queryState: QueryState) = listeners.forEach {
it.onQueryStateChange(queryState)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.masterwok.shrimplesearch.common.data.repositories

import android.content.Context
import com.masterwok.shrimplesearch.common.data.repositories.contracts.ConfigurationRepository
import com.masterwok.shrimplesearch.di.modules.RepositoryModule
import javax.inject.Inject
import javax.inject.Named

class SharedPreferencesConfigurationRepository @Inject constructor(
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document.

appContext: Context,
@Named(RepositoryModule.NAMED_SHARED_PREFERENCES_NAME) sharedPreferencesName: String
) : ConfigurationRepository {

private val sharedPreferences = appContext.getSharedPreferences(
sharedPreferencesName,
Context.MODE_PRIVATE
)

override suspend fun incrementResultTapCount() {
val resultItemCount = getResultItemTapCount()

sharedPreferences
.edit()
.putLong(NAME_RESULT_ITEM_TAP_COUNT, resultItemCount + 1L)
.apply()
}

override suspend fun getResultItemTapCount(): Long = sharedPreferences.getLong(
NAME_RESULT_ITEM_TAP_COUNT,
0
)

companion object {
private const val NAME_RESULT_ITEM_TAP_COUNT = "configuration.result_item_tap_count"
}

}
Original file line number Diff line number Diff line change
@@ -1,28 +1,53 @@
package com.masterwok.shrimplesearch.common.data.repositories

import android.content.Context
import android.content.SharedPreferences
import com.masterwok.shrimplesearch.R
import com.masterwok.shrimplesearch.common.constants.Theme
import com.masterwok.shrimplesearch.common.data.models.UserSettings
import com.masterwok.shrimplesearch.common.data.models.from
import com.masterwok.shrimplesearch.common.data.repositories.contracts.UserSettingsRepository
import com.masterwok.shrimplesearch.di.modules.RepositoryModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import javax.inject.Inject
import javax.inject.Named
import kotlin.system.measureTimeMillis

class SharedPreferencesUserSettingsRepository @Inject constructor(
appContext: Context,
@Named("shared_preferences_name") sharedPreferencesName: String,
@Named("default_user_settings") private val defaultUserSettings: UserSettings
@Named(RepositoryModule.NAMED_SHARED_PREFERENCES_NAME) sharedPreferencesName: String,
@Named(RepositoryModule.NAMED_DEFAULT_USER_SETTINGS) private val defaultUserSettings: UserSettings
) : UserSettingsRepository {

private val sharedPreferences = appContext.getSharedPreferences(
sharedPreferencesName,
Context.MODE_PRIVATE
)

@ExperimentalCoroutinesApi
override fun getUserSettingsAsFlow() = callbackFlow {
send(read())

val callback = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == NAME_USER_SETTINGS) {
sendBlocking(read())
}
}

sharedPreferences.registerOnSharedPreferenceChangeListener(callback)

awaitClose { sharedPreferences.unregisterOnSharedPreferenceChangeListener(callback) }
}

override fun read(): UserSettings {
val serialized = sharedPreferences
.getString(NAME_USER_SETTINGS, null)
Expand All @@ -38,7 +63,7 @@ class SharedPreferencesUserSettingsRepository @Inject constructor(
}
}

override fun update(userSettings: UserSettings) {
override suspend fun update(userSettings: UserSettings) = withContext(Dispatchers.IO) {
sharedPreferences
.edit()
.putString(NAME_USER_SETTINGS, Json.encodeToString(userSettings))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.masterwok.shrimplesearch.common.data.repositories.contracts

interface ConfigurationRepository {
suspend fun incrementResultTapCount()
suspend fun getResultItemTapCount(): Long
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.masterwok.shrimplesearch.common.data.repositories.contracts

import com.masterwok.shrimplesearch.common.data.models.UserSettings
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow

interface UserSettingsRepository {

fun read(): UserSettings

fun update(userSettings: UserSettings)
suspend fun update(userSettings: UserSettings)

fun getThemeId(): Int

fun getSplashThemeId(): Int

@ExperimentalCoroutinesApi
fun getUserSettingsAsFlow(): Flow<UserSettings>

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.masterwok.shrimplesearch.di.modules

import com.masterwok.shrimplesearch.common.DEFAULT_USER_SETTINGS
import com.masterwok.shrimplesearch.common.IN_APP_REVIEW_RESULT_ITEM_TAP_COUNT
import com.masterwok.shrimplesearch.common.SHARED_PREFERENCES_NAME
import com.masterwok.shrimplesearch.common.data.models.UserSettings
import com.masterwok.shrimplesearch.common.data.repositories.SharedPreferencesConfigurationRepository
import com.masterwok.shrimplesearch.common.data.repositories.SharedPreferencesUserSettingsRepository
import com.masterwok.shrimplesearch.common.data.repositories.contracts.ConfigurationRepository
import com.masterwok.shrimplesearch.common.data.repositories.contracts.UserSettingsRepository
import dagger.Binds
import dagger.Module
Expand All @@ -26,13 +29,29 @@ class RepositoryModule {
abstract fun bindSharedPreferencesUserSettingsRepository(
sharedPreferencesUserSettingsRepository: SharedPreferencesUserSettingsRepository
): UserSettingsRepository

@Singleton
@Binds
abstract fun bindConfigurationRepository(
sharedPreferencesUserConfigurationRepository: SharedPreferencesConfigurationRepository
): ConfigurationRepository
}

@Provides
@Named("shared_preferences_name")
@Named(NAMED_SHARED_PREFERENCES_NAME)
fun provideSharedPreferencesName(): String = SHARED_PREFERENCES_NAME

@Provides
@Named("default_user_settings")
@Named(NAMED_DEFAULT_USER_SETTINGS)
fun provideDefaultUserSettings(): UserSettings = DEFAULT_USER_SETTINGS

@Provides
@Named(NAMED_IN_APP_REVIEW_RESULT_ITEM_TAP_COUNT)
fun provideInAppReviewResultItemTapCount(): Int = IN_APP_REVIEW_RESULT_ITEM_TAP_COUNT

companion object {
const val NAMED_SHARED_PREFERENCES_NAME = "shared_preferences_name"
const val NAMED_DEFAULT_USER_SETTINGS = "default_user_settings"
const val NAMED_IN_APP_REVIEW_RESULT_ITEM_TAP_COUNT = "in_app_review_result_item_tap_count"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.masterwok.shrimplesearch.di.modules

import android.content.Context
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import com.masterwok.shrimplesearch.common.INDEXER_BLOCK_LIST
import com.masterwok.shrimplesearch.common.data.repositories.JackettServiceImpl
import com.masterwok.shrimplesearch.common.data.repositories.contracts.JackettService
Expand Down Expand Up @@ -44,4 +47,10 @@ class ServiceModule {
INDEXER_BLOCK_LIST
)

@Suppress("unused")
@Singleton
@Provides
fun provideReviewManager(
appContext: Context
): ReviewManager = ReviewManagerFactory.create(appContext)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,6 @@ class AboutFragment : Fragment() {

private fun subscribeToViewComponents() {
buttonViewOnGitHub.setOnClickListener { openGitHubProjectUri() }
buttonViewReview.setOnClickListener { openReviewPlayStore() }
buttonViewShare.setOnClickListener { onShareButtonTapped() }
}

private fun onShareButtonTapped() = activity.notNull { activity ->
analyticService.logEvent(AnalyticEvent.ShareManeki)

val chooserTitle = activity.getString(R.string.share_chooser_title)
val shareText = activity.getString(
R.string.share_text,
activity.getPlayStoreUri().toString()
)

val intent = ShareCompat
.IntentBuilder
.from(activity)
.setType("text/plain")
.setText(shareText)
.intent

activity.startActivity(Intent.createChooser(intent, chooserTitle))
}

private fun openGitHubProjectUri() = context.notNull { context ->
Expand All @@ -96,15 +75,6 @@ class AboutFragment : Fragment() {
}
}

private fun openReviewPlayStore() = context.notNull { context ->
try {
context.startPlayStoreActivity()
} catch (exception: ActivityNotFoundException) {
analyticService.logException(exception, "No activity found to handle open GitHub Uri")
presentUnableToOpenPlayStoreDialog()
}
}

private fun presentUnableToOpenPlayStoreDialog() = context.notNull { context ->
MaterialDialog(context).show {
title(res = R.string.dialog_header_whoops)
Expand Down
Loading