mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-24 03:52:36 +01:00
Added the new compose screen with its components and events
This commit is contained in:
parent
2eb85bd804
commit
c3aa4e78ec
@ -310,9 +310,11 @@ dependencies {
|
||||
// Coroutines interop
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.8.1'
|
||||
|
||||
// Hilt
|
||||
// Hilt & Dagger
|
||||
implementation("com.google.dagger:hilt-android:2.51.1")
|
||||
kapt("com.google.dagger:hilt-compiler:2.51.1")
|
||||
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
|
||||
kapt("androidx.hilt:hilt-compiler:1.2.0")
|
||||
|
||||
// Scroll
|
||||
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
|
||||
|
@ -94,6 +94,9 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
|
@ -0,0 +1,27 @@
|
||||
package org.schabi.newpipe.dependency_injection
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.schabi.newpipe.error.usecases.OpenErrorActivity
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AppModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOpenActivity(
|
||||
@ApplicationContext context: Context,
|
||||
): OpenErrorActivity = OpenErrorActivity(context)
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package org.schabi.newpipe.dependency_injection
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.schabi.newpipe.database.AppDatabase
|
||||
import org.schabi.newpipe.database.AppDatabase.DATABASE_NAME
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_1_2
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_2_3
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_3_4
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_4_5
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_5_6
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_6_7
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_7_8
|
||||
import org.schabi.newpipe.database.Migrations.MIGRATION_8_9
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO
|
||||
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO
|
||||
import org.schabi.newpipe.database.stream.dao.StreamStateDAO
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
class DatabaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppDatabase(@ApplicationContext appContext: Context): AppDatabase =
|
||||
Room.databaseBuilder(
|
||||
appContext,
|
||||
AppDatabase::class.java,
|
||||
DATABASE_NAME
|
||||
).addMigrations(
|
||||
MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
|
||||
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9
|
||||
).build()
|
||||
|
||||
@Provides
|
||||
fun provideStreamStateDao(appDatabase: AppDatabase): StreamStateDAO =
|
||||
appDatabase.streamStateDAO()
|
||||
|
||||
@Provides
|
||||
fun providesStreamDao(appDatabase: AppDatabase): StreamDAO = appDatabase.streamDAO()
|
||||
|
||||
@Provides
|
||||
fun provideStreamHistoryDao(appDatabase: AppDatabase): StreamHistoryDAO =
|
||||
appDatabase.streamHistoryDAO()
|
||||
|
||||
@Provides
|
||||
fun provideSearchHistoryDao(appDatabase: AppDatabase): SearchHistoryDAO =
|
||||
appDatabase.searchHistoryDAO()
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.schabi.newpipe.error.usecases
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import org.schabi.newpipe.error.ErrorActivity
|
||||
import org.schabi.newpipe.error.ErrorInfo
|
||||
|
||||
class OpenErrorActivity(
|
||||
private val context: Context,
|
||||
) {
|
||||
operator fun invoke(errorInfo: ErrorInfo) {
|
||||
val intent = Intent(context, ErrorActivity::class.java)
|
||||
intent.putExtra(ErrorActivity.ERROR_INFO, errorInfo)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
|
||||
import com.evernote.android.state.State;
|
||||
@ -27,6 +28,7 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||
import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder;
|
||||
@ -35,7 +37,6 @@ import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.settings.HistorySettingsFragment;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.PlayButtonHelper;
|
||||
@ -161,14 +162,72 @@ public class StatisticsPlaylistFragment
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_history_clear) {
|
||||
HistorySettingsFragment
|
||||
.openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
|
||||
openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void openDeleteWatchHistoryDialog(
|
||||
@NonNull final Context context,
|
||||
final HistoryRecordManager recordManager,
|
||||
final CompositeDisposable disposables
|
||||
) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.delete_view_history_alert)
|
||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
||||
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager));
|
||||
disposables.add(getWholeStreamHistoryDisposable(context, recordManager));
|
||||
disposables.add(getRemoveOrphanedRecordsDisposable(context, recordManager));
|
||||
}))
|
||||
.show();
|
||||
}
|
||||
|
||||
private static Disposable getDeletePlaybackStatesDisposable(
|
||||
@NonNull final Context context,
|
||||
final HistoryRecordManager recordManager
|
||||
) {
|
||||
return recordManager.deleteCompleteStreamStateHistory()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(context,
|
||||
R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorUtil.openActivity(context,
|
||||
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete playback states"))
|
||||
);
|
||||
}
|
||||
|
||||
private static Disposable getWholeStreamHistoryDisposable(
|
||||
@NonNull final Context context,
|
||||
final HistoryRecordManager recordManager
|
||||
) {
|
||||
return recordManager.deleteWholeStreamHistory()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(context,
|
||||
R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorUtil.openActivity(context,
|
||||
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete from history"))
|
||||
);
|
||||
}
|
||||
|
||||
private static Disposable getRemoveOrphanedRecordsDisposable(
|
||||
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||
return recordManager.removeOrphanedRecords()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> {
|
||||
},
|
||||
throwable -> ErrorUtil.openActivity(context,
|
||||
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||
"Clear orphaned records"))
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Loading
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -43,6 +43,9 @@ import org.schabi.newpipe.views.FocusOverlayView;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 31.08.15.
|
||||
*
|
||||
@ -63,6 +66,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class SettingsActivity extends AppCompatActivity implements
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||
PreferenceSearchResultListener {
|
||||
|
@ -0,0 +1,85 @@
|
||||
package org.schabi.newpipe.settings.components.irreversible_preference
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.ui.theme.SizeTokens.SpacingExtraSmall
|
||||
import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium
|
||||
|
||||
@Composable
|
||||
fun IrreversiblePreferenceComponent(
|
||||
title: String,
|
||||
summary: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
val clickModifier = if (enabled) {
|
||||
Modifier.clickable { onClick() }
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Row(
|
||||
modifier = clickModifier.then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val alpha by remember {
|
||||
derivedStateOf {
|
||||
if (enabled) 1f else 0.38f
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(SpacingMedium)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.alpha(alpha),
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(SpacingExtraSmall))
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.alpha(alpha * 0.6f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF)
|
||||
@Composable
|
||||
private fun IrreversiblePreferenceComponentPreview() {
|
||||
val title = "Wipe cached metadata"
|
||||
val summary = "Remove all cached webpage data"
|
||||
AppTheme {
|
||||
Column {
|
||||
|
||||
IrreversiblePreferenceComponent(
|
||||
title = title,
|
||||
summary = summary,
|
||||
onClick = {},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
IrreversiblePreferenceComponent(
|
||||
title = title,
|
||||
summary = summary,
|
||||
onClick = {},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package org.schabi.newpipe.settings.components.switch_preference
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.ui.theme.SizeTokens.SpacingExtraSmall
|
||||
import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium
|
||||
|
||||
@Composable
|
||||
fun SwitchPreferenceComponent(
|
||||
title: String,
|
||||
summary: String,
|
||||
isChecked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(SpacingMedium)
|
||||
) {
|
||||
Text(text = title)
|
||||
Spacer(modifier = Modifier.padding(SpacingExtraSmall))
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.alpha(0.6f)
|
||||
)
|
||||
}
|
||||
|
||||
Switch(
|
||||
checked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = Modifier.padding(SpacingMedium)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF)
|
||||
@Composable
|
||||
private fun SwitchPreferenceComponentPreview() {
|
||||
val title = "Watch history"
|
||||
val subtitle = "Keep track of watched videos"
|
||||
var isChecked = false
|
||||
AppTheme {
|
||||
SwitchPreferenceComponent(
|
||||
title = title,
|
||||
summary = subtitle,
|
||||
isChecked = isChecked,
|
||||
onCheckedChange = {
|
||||
isChecked = it
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
package org.schabi.newpipe.settings.dependency_injection
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO
|
||||
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO
|
||||
import org.schabi.newpipe.database.stream.dao.StreamStateDAO
|
||||
import org.schabi.newpipe.error.usecases.OpenErrorActivity
|
||||
import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository
|
||||
import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepositoryImpl
|
||||
import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteSearchHistory
|
||||
import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteStreamStateHistory
|
||||
import org.schabi.newpipe.settings.domain.usecases.DeletePlaybackStates
|
||||
import org.schabi.newpipe.settings.domain.usecases.DeleteWatchHistory
|
||||
import org.schabi.newpipe.settings.domain.usecases.RemoveOrphanedRecords
|
||||
import org.schabi.newpipe.settings.domain.usecases.get_preference.GetPreference
|
||||
import org.schabi.newpipe.settings.domain.usecases.get_preference.GetPreferenceImpl
|
||||
import org.schabi.newpipe.settings.domain.usecases.update_preference.UpdatePreference
|
||||
import org.schabi.newpipe.settings.domain.usecases.update_preference.UpdatePreferenceImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object SettingsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetBooleanPreference(
|
||||
sharedPreferences: SharedPreferences,
|
||||
@ApplicationContext context: Context,
|
||||
): GetPreference<Boolean> = GetPreferenceImpl(sharedPreferences, context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStringPreference(
|
||||
sharedPreferences: SharedPreferences,
|
||||
@ApplicationContext context: Context,
|
||||
): GetPreference<String> = GetPreferenceImpl(sharedPreferences, context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateBooleanPreference(
|
||||
sharedPreferences: SharedPreferences,
|
||||
@ApplicationContext context: Context,
|
||||
): UpdatePreference<Boolean> = UpdatePreferenceImpl(context, sharedPreferences) { key, value ->
|
||||
putBoolean(
|
||||
key,
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateStringPreference(
|
||||
sharedPreferences: SharedPreferences,
|
||||
@ApplicationContext context: Context,
|
||||
): UpdatePreference<String> = UpdatePreferenceImpl(context, sharedPreferences) { key, value ->
|
||||
putString(
|
||||
key,
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateIntPreference(
|
||||
sharedPreferences: SharedPreferences,
|
||||
@ApplicationContext context: Context,
|
||||
): UpdatePreference<Int> = UpdatePreferenceImpl(context, sharedPreferences) { key, value ->
|
||||
putInt(key, value)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHistoryRecordRepository(
|
||||
streamStateDao: StreamStateDAO,
|
||||
streamHistoryDAO: StreamHistoryDAO,
|
||||
streamDAO: StreamDAO,
|
||||
searchHistoryDAO: SearchHistoryDAO,
|
||||
): HistoryRecordRepository = HistoryRecordRepositoryImpl(
|
||||
streamStateDao = streamStateDao,
|
||||
streamHistoryDAO = streamHistoryDAO,
|
||||
streamDAO = streamDAO,
|
||||
searchHistoryDAO = searchHistoryDAO,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDeletePlaybackStatesUseCase(
|
||||
historyRecordRepository: HistoryRecordRepository,
|
||||
): DeletePlaybackStates = DeletePlaybackStates(
|
||||
historyRecordRepository = historyRecordRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDeleteWholeStreamHistoryUseCase(
|
||||
historyRecordRepository: HistoryRecordRepository,
|
||||
): DeleteCompleteStreamStateHistory = DeleteCompleteStreamStateHistory(
|
||||
historyRecordRepository = historyRecordRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRemoveOrphanedRecordsUseCase(
|
||||
historyRecordRepository: HistoryRecordRepository,
|
||||
): RemoveOrphanedRecords = RemoveOrphanedRecords(
|
||||
historyRecordRepository = historyRecordRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDeleteCompleteSearchHistoryUseCase(
|
||||
historyRecordRepository: HistoryRecordRepository,
|
||||
): DeleteCompleteSearchHistory = DeleteCompleteSearchHistory(
|
||||
historyRecordRepository = historyRecordRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDeleteWatchHistoryUseCase(
|
||||
deletePlaybackStates: DeletePlaybackStates,
|
||||
deleteCompleteStreamStateHistory: DeleteCompleteStreamStateHistory,
|
||||
removeOrphanedRecords: RemoveOrphanedRecords,
|
||||
openErrorActivity: OpenErrorActivity,
|
||||
): DeleteWatchHistory = DeleteWatchHistory(
|
||||
deletePlaybackStates = deletePlaybackStates,
|
||||
deleteCompleteStreamStateHistory = deleteCompleteStreamStateHistory,
|
||||
removeOrphanedRecords = removeOrphanedRecords,
|
||||
openErrorActivity = openErrorActivity
|
||||
)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.schabi.newpipe.settings.domain.repositories
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface HistoryRecordRepository {
|
||||
fun deleteCompleteStreamState(dispatcher: CoroutineDispatcher): Flow<Int>
|
||||
fun deleteWholeStreamHistory(dispatcher: CoroutineDispatcher): Flow<Int>
|
||||
fun removeOrphanedRecords(dispatcher: CoroutineDispatcher): Flow<Int>
|
||||
fun deleteCompleteSearchHistory(dispatcher: CoroutineDispatcher): Flow<Int>
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.schabi.newpipe.settings.domain.repositories
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
||||
|
||||
class HistoryRecordRepositoryFake : HistoryRecordRepository {
|
||||
private val _searchHistory: MutableStateFlow<List<SearchHistoryEntry>> = MutableStateFlow(
|
||||
emptyList()
|
||||
)
|
||||
val searchHistory = _searchHistory.asStateFlow()
|
||||
private val _streamHistory = MutableStateFlow<List<StreamHistoryEntity>>(emptyList())
|
||||
val streamHistory = _streamHistory.asStateFlow()
|
||||
private val _streams = MutableStateFlow<List<StreamEntity>>(emptyList())
|
||||
val streams = _streams.asStateFlow()
|
||||
private val _streamStates = MutableStateFlow<List<StreamStateEntity>>(emptyList())
|
||||
val streamStates = _streamStates.asStateFlow()
|
||||
|
||||
override fun deleteCompleteStreamState(dispatcher: CoroutineDispatcher): Flow<Int> = flow {
|
||||
val count = streamStates.value.size
|
||||
_streamStates.update {
|
||||
emptyList()
|
||||
}
|
||||
emit(count)
|
||||
}.flowOn(dispatcher)
|
||||
|
||||
override fun deleteWholeStreamHistory(dispatcher: CoroutineDispatcher): Flow<Int> = flow {
|
||||
val count = streamHistory.value.size
|
||||
_streamHistory.update {
|
||||
emptyList()
|
||||
}
|
||||
emit(count)
|
||||
}.flowOn(dispatcher)
|
||||
|
||||
override fun removeOrphanedRecords(dispatcher: CoroutineDispatcher): Flow<Int> = flow {
|
||||
val orphanedStreams = streams.value.filter { stream ->
|
||||
!streamHistory.value.any { it.streamUid == stream.uid }
|
||||
}
|
||||
|
||||
val deletedCount = orphanedStreams.size
|
||||
|
||||
_streams.update { oldStreams ->
|
||||
oldStreams.filter { it !in orphanedStreams }
|
||||
}
|
||||
|
||||
emit(deletedCount)
|
||||
}.flowOn(dispatcher)
|
||||
|
||||
override fun deleteCompleteSearchHistory(dispatcher: CoroutineDispatcher): Flow<Int> = flow {
|
||||
val count = searchHistory.value.size
|
||||
_searchHistory.update {
|
||||
emptyList()
|
||||
}
|
||||
emit(count)
|
||||
}.flowOn(dispatcher)
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.schabi.newpipe.settings.domain.repositories
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO
|
||||
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO
|
||||
import org.schabi.newpipe.database.stream.dao.StreamStateDAO
|
||||
|
||||
class HistoryRecordRepositoryImpl(
|
||||
private val streamStateDao: StreamStateDAO,
|
||||
private val streamHistoryDAO: StreamHistoryDAO,
|
||||
private val streamDAO: StreamDAO,
|
||||
private val searchHistoryDAO: SearchHistoryDAO,
|
||||
) : HistoryRecordRepository {
|
||||
override fun deleteCompleteStreamState(dispatcher: CoroutineDispatcher): Flow<Int> =
|
||||
flow {
|
||||
val deletedCount = streamStateDao.deleteAll()
|
||||
emit(deletedCount)
|
||||
}.flowOn(dispatcher)
|
||||
|
||||
override fun deleteWholeStreamHistory(dispatcher: CoroutineDispatcher): Flow<Int> =
|
||||
flow {
|
||||
val deletedCount = streamHistoryDAO.deleteAll()
|
||||
emit(deletedCount)
|
||||
}.flowOn(dispatcher)
|
||||
|
||||
override fun removeOrphanedRecords(dispatcher: CoroutineDispatcher): Flow<Int> = flow {
|
||||
val deletedCount = streamDAO.deleteOrphans()
|
||||
emit(deletedCount)
|
||||
}.flowOn(dispatcher)
|
||||
|
||||
override fun deleteCompleteSearchHistory(dispatcher: CoroutineDispatcher): Flow<Int> = flow {
|
||||
val deletedCount = searchHistoryDAO.deleteAll()
|
||||
emit(deletedCount)
|
||||
}.flowOn(dispatcher)
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.take
|
||||
import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository
|
||||
|
||||
class DeleteCompleteSearchHistory(
|
||||
private val historyRecordRepository: HistoryRecordRepository,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
dispatcher: CoroutineDispatcher,
|
||||
onError: (Throwable) -> Unit,
|
||||
onSuccess: () -> Unit,
|
||||
) = historyRecordRepository.deleteCompleteSearchHistory(dispatcher).catch { error ->
|
||||
onError(error)
|
||||
}.take(1).collect {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.take
|
||||
import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository
|
||||
|
||||
class DeleteCompleteStreamStateHistory(
|
||||
private val historyRecordRepository: HistoryRecordRepository,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
dispatcher: CoroutineDispatcher,
|
||||
onError: (Throwable) -> Unit,
|
||||
onSuccess: () -> Unit,
|
||||
) = historyRecordRepository.deleteWholeStreamHistory(dispatcher).catch {
|
||||
onError(it)
|
||||
}.take(1).collect {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.take
|
||||
import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository
|
||||
|
||||
class DeletePlaybackStates(
|
||||
private val historyRecordRepository: HistoryRecordRepository,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
dispatcher: CoroutineDispatcher,
|
||||
onError: (Throwable) -> Unit,
|
||||
onSuccess: () -> Unit,
|
||||
) = historyRecordRepository.deleteCompleteStreamState(dispatcher).catch {
|
||||
onError(it)
|
||||
}.take(1).collect {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.schabi.newpipe.error.ErrorInfo
|
||||
import org.schabi.newpipe.error.UserAction
|
||||
import org.schabi.newpipe.error.usecases.OpenErrorActivity
|
||||
|
||||
class DeleteWatchHistory(
|
||||
private val deletePlaybackStates: DeletePlaybackStates,
|
||||
private val deleteCompleteStreamStateHistory: DeleteCompleteStreamStateHistory,
|
||||
private val removeOrphanedRecords: RemoveOrphanedRecords,
|
||||
private val openErrorActivity: OpenErrorActivity,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
onDeletePlaybackStates: () -> Unit,
|
||||
onDeleteWholeStreamHistory: () -> Unit,
|
||||
onRemoveOrphanedRecords: () -> Unit,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) = coroutineScope {
|
||||
launch {
|
||||
deletePlaybackStates(
|
||||
dispatcher,
|
||||
onError = { error ->
|
||||
openErrorActivity(
|
||||
ErrorInfo(
|
||||
error,
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete playback states"
|
||||
)
|
||||
)
|
||||
},
|
||||
onSuccess = onDeletePlaybackStates
|
||||
)
|
||||
}
|
||||
launch {
|
||||
deleteCompleteStreamStateHistory(
|
||||
dispatcher,
|
||||
onError = { error ->
|
||||
openErrorActivity(
|
||||
ErrorInfo(
|
||||
error,
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete from history"
|
||||
)
|
||||
)
|
||||
},
|
||||
onSuccess = onDeleteWholeStreamHistory
|
||||
)
|
||||
}
|
||||
launch {
|
||||
removeOrphanedRecords(
|
||||
dispatcher,
|
||||
onError = { error ->
|
||||
openErrorActivity(
|
||||
ErrorInfo(
|
||||
error,
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"Clear orphaned records"
|
||||
)
|
||||
)
|
||||
},
|
||||
onSuccess = onRemoveOrphanedRecords
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.take
|
||||
import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository
|
||||
|
||||
class RemoveOrphanedRecords(
|
||||
private val historyRecordRepository: HistoryRecordRepository,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
dispatcher: CoroutineDispatcher,
|
||||
onError: (Throwable) -> Unit,
|
||||
onSuccess: () -> Unit,
|
||||
) =
|
||||
historyRecordRepository.removeOrphanedRecords(dispatcher).catch {
|
||||
onError(it)
|
||||
}.take(1).collect {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases.get_preference
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
fun interface GetPreference<T> {
|
||||
operator fun invoke(key: Int, defaultValue: T): Flow<T>
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases.get_preference
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetPreferenceFake<T>(
|
||||
private val preferences: MutableStateFlow<MutableMap<Int, T>>,
|
||||
) : GetPreference<T> {
|
||||
override fun invoke(key: Int, defaultValue: T): Flow<T> {
|
||||
return preferences.asStateFlow().map {
|
||||
it[key] ?: defaultValue
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases.get_preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
|
||||
class GetPreferenceImpl<T>(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val context: Context,
|
||||
) : GetPreference<T> {
|
||||
override fun invoke(key: Int, defaultValue: T): Flow<T> {
|
||||
val keyString = context.getString(key)
|
||||
return sharedPreferences.getFlowForKey(keyString, defaultValue)
|
||||
}
|
||||
|
||||
private fun <T> SharedPreferences.getFlowForKey(key: String, defaultValue: T) = callbackFlow {
|
||||
val listener =
|
||||
SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->
|
||||
if (key == changedKey) {
|
||||
val updated = getPreferenceValue(key, defaultValue)
|
||||
trySend(updated)
|
||||
}
|
||||
}
|
||||
registerOnSharedPreferenceChangeListener(listener)
|
||||
println("Current value for $key: ${getPreferenceValue(key, defaultValue)}")
|
||||
if (contains(key)) {
|
||||
send(getPreferenceValue(key, defaultValue))
|
||||
}
|
||||
awaitClose {
|
||||
unregisterOnSharedPreferenceChangeListener(listener)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> SharedPreferences.getPreferenceValue(key: String, defaultValue: T): T {
|
||||
return when (defaultValue) {
|
||||
is Boolean -> getBoolean(key, defaultValue) as T
|
||||
is Int -> getInt(key, defaultValue) as T
|
||||
is Long -> getLong(key, defaultValue) as T
|
||||
is Float -> getFloat(key, defaultValue) as T
|
||||
is String -> getString(key, defaultValue) as T
|
||||
else -> throw IllegalArgumentException("Unsupported type")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases.update_preference
|
||||
|
||||
fun interface UpdatePreference<T> {
|
||||
suspend operator fun invoke(key: Int, value: T)
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases.update_preference
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class UpdatePreferenceFake<T>(
|
||||
private val preferences: MutableStateFlow<MutableMap<Int, T>>,
|
||||
) : UpdatePreference<T> {
|
||||
override suspend fun invoke(key: Int, value: T) {
|
||||
preferences.update {
|
||||
it.apply {
|
||||
put(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.schabi.newpipe.settings.domain.usecases.update_preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
|
||||
class UpdatePreferenceImpl<T>(
|
||||
private val context: Context,
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor,
|
||||
) : UpdatePreference<T> {
|
||||
override suspend operator fun invoke(key: Int, value: T) {
|
||||
val stringKey = context.getString(key)
|
||||
sharedPreferences.edit {
|
||||
setter(stringKey, value)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.util.KEY_SERVICE_ID
|
||||
import org.schabi.newpipe.util.KEY_URL
|
||||
|
||||
class HistoryCacheFragment : Fragment() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
) = ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
AppTheme {
|
||||
HistoryCacheSettingsScreen(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(serviceId: Int, url: String?) = CommentsFragment().apply {
|
||||
arguments = bundleOf(KEY_SERVICE_ID to serviceId, KEY_URL to url)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.components.CachePreferencesComponent
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.components.HistoryPreferencesComponent
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowClearWatchHistorySnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeletePlaybackSnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeleteSearchHistorySnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowReCaptchaCookiesSnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.state.SwitchPreferencesUiState
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun HistoryCacheSettingsScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: HistoryCacheSettingsViewModel = hiltViewModel(),
|
||||
) {
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
val playBackPositionsDeleted = stringResource(R.string.watch_history_states_deleted)
|
||||
val watchHistoryDeleted = stringResource(R.string.watch_history_deleted)
|
||||
val wipeCachedMetadataSnackbar = stringResource(R.string.metadata_cache_wipe_complete_notice)
|
||||
val deleteSearchHistory = stringResource(R.string.search_history_deleted)
|
||||
val clearReCaptchaCookiesSnackbar = stringResource(R.string.recaptcha_cookies_cleared)
|
||||
|
||||
LaunchedEffect(key1 = true) {
|
||||
viewModel.onInit()
|
||||
viewModel.eventFlow.collect { event ->
|
||||
val message = when (event) {
|
||||
is ShowDeletePlaybackSnackbar -> playBackPositionsDeleted
|
||||
is ShowClearWatchHistorySnackbar -> watchHistoryDeleted
|
||||
is ShowWipeCachedMetadataSnackbar -> wipeCachedMetadataSnackbar
|
||||
is ShowDeleteSearchHistorySnackbar -> deleteSearchHistory
|
||||
is ShowReCaptchaCookiesSnackbar -> clearReCaptchaCookiesSnackbar
|
||||
}
|
||||
|
||||
snackBarHostState.showSnackbar(message)
|
||||
}
|
||||
}
|
||||
|
||||
val switchPreferencesUiState by viewModel.switchState.collectAsState()
|
||||
val recaptchaCookiesEnabled by viewModel.captchaCookies.collectAsState()
|
||||
HistoryCacheComponent(
|
||||
switchPreferences = switchPreferencesUiState,
|
||||
recaptchaCookiesEnabled = recaptchaCookiesEnabled,
|
||||
onEvent = { viewModel.onEvent(it) },
|
||||
snackBarHostState = snackBarHostState,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HistoryCacheComponent(
|
||||
switchPreferences: SwitchPreferencesUiState,
|
||||
recaptchaCookiesEnabled: Boolean,
|
||||
onEvent: (HistoryCacheEvent) -> Unit,
|
||||
snackBarHostState: SnackbarHostState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
snackbarHost = {
|
||||
SnackbarHost(snackBarHostState)
|
||||
}
|
||||
) { padding ->
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.verticalScroll(scrollState),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
HistoryPreferencesComponent(
|
||||
state = switchPreferences,
|
||||
onEvent = { key, value ->
|
||||
onEvent(HistoryCacheEvent.OnUpdateBooleanPreference(key, value))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
HorizontalDivider(Modifier.fillMaxWidth())
|
||||
CachePreferencesComponent(
|
||||
recaptchaCookiesEnabled = recaptchaCookiesEnabled,
|
||||
onEvent = { onEvent(it) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun HistoryCacheComponentPreview() {
|
||||
val state by remember {
|
||||
mutableStateOf(
|
||||
SwitchPreferencesUiState()
|
||||
)
|
||||
}
|
||||
AppTheme(
|
||||
useDarkTheme = false
|
||||
) {
|
||||
Surface {
|
||||
HistoryCacheComponent(
|
||||
switchPreferences = state,
|
||||
recaptchaCookiesEnabled = false,
|
||||
onEvent = {
|
||||
},
|
||||
snackBarHostState = SnackbarHostState(),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.schabi.newpipe.DownloaderImpl
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.error.ErrorInfo
|
||||
import org.schabi.newpipe.error.ReCaptchaActivity
|
||||
import org.schabi.newpipe.error.UserAction
|
||||
import org.schabi.newpipe.error.usecases.OpenErrorActivity
|
||||
import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteSearchHistory
|
||||
import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteStreamStateHistory
|
||||
import org.schabi.newpipe.settings.domain.usecases.DeleteWatchHistory
|
||||
import org.schabi.newpipe.settings.domain.usecases.get_preference.GetPreference
|
||||
import org.schabi.newpipe.settings.domain.usecases.update_preference.UpdatePreference
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickClearSearchHistory
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickClearWatchHistory
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickDeletePlaybackPositions
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickReCaptchaCookies
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickWipeCachedMetadata
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnUpdateBooleanPreference
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowClearWatchHistorySnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeletePlaybackSnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeleteSearchHistorySnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.state.SwitchPreferencesUiState
|
||||
import org.schabi.newpipe.util.InfoCache
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class HistoryCacheSettingsViewModel @Inject constructor(
|
||||
private val updateStringPreference: UpdatePreference<String>,
|
||||
private val updateBooleanPreference: UpdatePreference<Boolean>,
|
||||
private val getStringPreference: GetPreference<String>,
|
||||
private val getBooleanPreference: GetPreference<Boolean>,
|
||||
private val deleteWatchHistory: DeleteWatchHistory,
|
||||
private val deleteCompleteStreamStateHistory: DeleteCompleteStreamStateHistory,
|
||||
private val deleteCompleteSearchHistory: DeleteCompleteSearchHistory,
|
||||
private val openErrorActivity: OpenErrorActivity,
|
||||
) : ViewModel() {
|
||||
private val _switchState = MutableStateFlow(SwitchPreferencesUiState())
|
||||
val switchState: StateFlow<SwitchPreferencesUiState> = _switchState.asStateFlow()
|
||||
|
||||
private val _captchaCookies = MutableStateFlow(false)
|
||||
val captchaCookies: StateFlow<Boolean> = _captchaCookies.asStateFlow()
|
||||
|
||||
private val _eventFlow = MutableSharedFlow<HistoryCacheUiEvent>()
|
||||
val eventFlow = _eventFlow.asSharedFlow()
|
||||
|
||||
fun onInit() {
|
||||
|
||||
viewModelScope.launch {
|
||||
val flow = getStringPreference(R.string.recaptcha_cookies_key, "")
|
||||
flow.collect { preference ->
|
||||
_captchaCookies.update {
|
||||
preference.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
getBooleanPreference(R.string.enable_watch_history_key, true).collect { preference ->
|
||||
_switchState.update { oldState ->
|
||||
oldState.copy(
|
||||
watchHistoryEnabled = preference
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
getBooleanPreference(R.string.enable_playback_resume_key, true).collect { preference ->
|
||||
_switchState.update { oldState ->
|
||||
oldState.copy(
|
||||
resumePlaybackEnabled = preference
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
getBooleanPreference(
|
||||
R.string.enable_playback_state_lists_key,
|
||||
true
|
||||
).collect { preference ->
|
||||
_switchState.update { oldState ->
|
||||
oldState.copy(
|
||||
positionsInListsEnabled = preference
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
getBooleanPreference(R.string.enable_search_history_key, true).collect { preference ->
|
||||
_switchState.update { oldState ->
|
||||
oldState.copy(
|
||||
searchHistoryEnabled = preference
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEvent(event: HistoryCacheEvent) {
|
||||
when (event) {
|
||||
is OnUpdateBooleanPreference -> {
|
||||
viewModelScope.launch {
|
||||
updateBooleanPreference(event.key, event.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
is OnClickWipeCachedMetadata -> {
|
||||
InfoCache.getInstance().clearCache()
|
||||
viewModelScope.launch {
|
||||
_eventFlow.emit(ShowWipeCachedMetadataSnackbar)
|
||||
}
|
||||
}
|
||||
|
||||
is OnClickClearWatchHistory -> {
|
||||
viewModelScope.launch {
|
||||
deleteWatchHistory(
|
||||
onDeletePlaybackStates = {
|
||||
viewModelScope.launch {
|
||||
_eventFlow.emit(ShowDeletePlaybackSnackbar)
|
||||
}
|
||||
},
|
||||
onDeleteWholeStreamHistory = {
|
||||
viewModelScope.launch {
|
||||
_eventFlow.emit(ShowClearWatchHistorySnackbar)
|
||||
}
|
||||
},
|
||||
onRemoveOrphanedRecords = {
|
||||
// TODO: ask why original in android fragments did nothing
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is OnClickDeletePlaybackPositions -> {
|
||||
viewModelScope.launch {
|
||||
deleteCompleteStreamStateHistory(
|
||||
Dispatchers.IO,
|
||||
onError = { error ->
|
||||
openErrorActivity(
|
||||
ErrorInfo(
|
||||
error,
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete playback states"
|
||||
)
|
||||
)
|
||||
},
|
||||
onSuccess = {
|
||||
viewModelScope.launch {
|
||||
_eventFlow.emit(ShowDeletePlaybackSnackbar)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is OnClickClearSearchHistory -> {
|
||||
viewModelScope.launch {
|
||||
deleteCompleteSearchHistory(
|
||||
dispatcher = Dispatchers.IO,
|
||||
onError = { error ->
|
||||
openErrorActivity(
|
||||
ErrorInfo(
|
||||
error,
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"Delete search history"
|
||||
)
|
||||
)
|
||||
},
|
||||
onSuccess = {
|
||||
viewModelScope.launch {
|
||||
_eventFlow.emit(ShowDeleteSearchHistorySnackbar)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is OnClickReCaptchaCookies -> {
|
||||
viewModelScope.launch {
|
||||
updateStringPreference(event.key, "")
|
||||
DownloaderImpl.getInstance()
|
||||
.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, "")
|
||||
_eventFlow.emit(ShowWipeCachedMetadataSnackbar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.settings.components.irreversible_preference.IrreversiblePreferenceComponent
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium
|
||||
|
||||
@Composable
|
||||
fun CachePreferencesComponent(
|
||||
recaptchaCookiesEnabled: Boolean,
|
||||
onEvent: (HistoryCacheEvent) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var dialogTitle by remember { mutableStateOf("") }
|
||||
var dialogOnClick by remember { mutableStateOf({}) }
|
||||
var isDialogVisible by remember { mutableStateOf(false) }
|
||||
|
||||
val deleteViewHistory = stringResource(id = R.string.delete_view_history_alert)
|
||||
val deletePlayBacks = stringResource(id = R.string.delete_playback_states_alert)
|
||||
val deleteSearchHistory = stringResource(id = R.string.delete_search_history_alert)
|
||||
|
||||
val onOpenDialog: (String, HistoryCacheEvent) -> Unit = { title, eventType ->
|
||||
dialogTitle = title
|
||||
isDialogVisible = true
|
||||
dialogOnClick = {
|
||||
onEvent(eventType)
|
||||
isDialogVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = R.string.settings_category_clear_data_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(SpacingMedium)
|
||||
)
|
||||
IrreversiblePreferenceComponent(
|
||||
title = stringResource(id = R.string.metadata_cache_wipe_title),
|
||||
summary = stringResource(id = R.string.metadata_cache_wipe_summary),
|
||||
onClick = {
|
||||
onEvent(HistoryCacheEvent.OnClickWipeCachedMetadata(R.string.metadata_cache_wipe_key))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
IrreversiblePreferenceComponent(
|
||||
title = stringResource(id = R.string.clear_views_history_title),
|
||||
summary = stringResource(id = R.string.clear_views_history_summary),
|
||||
onClick = {
|
||||
onOpenDialog(
|
||||
deleteViewHistory,
|
||||
HistoryCacheEvent.OnClickClearWatchHistory(R.string.clear_views_history_key)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
IrreversiblePreferenceComponent(
|
||||
title = stringResource(id = R.string.clear_playback_states_title),
|
||||
summary = stringResource(id = R.string.clear_playback_states_summary),
|
||||
onClick = {
|
||||
onOpenDialog(
|
||||
deletePlayBacks,
|
||||
HistoryCacheEvent.OnClickDeletePlaybackPositions(R.string.clear_playback_states_key)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
IrreversiblePreferenceComponent(
|
||||
title = stringResource(id = R.string.clear_search_history_title),
|
||||
summary = stringResource(id = R.string.clear_search_history_summary),
|
||||
onClick = {
|
||||
onOpenDialog(
|
||||
deleteSearchHistory,
|
||||
HistoryCacheEvent.OnClickClearSearchHistory(R.string.clear_search_history_key)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
IrreversiblePreferenceComponent(
|
||||
title = stringResource(id = R.string.clear_cookie_title),
|
||||
summary = stringResource(id = R.string.clear_cookie_summary),
|
||||
onClick = {
|
||||
onEvent(HistoryCacheEvent.OnClickReCaptchaCookies(R.string.recaptcha_cookies_key))
|
||||
},
|
||||
enabled = recaptchaCookiesEnabled,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
if (isDialogVisible) {
|
||||
CacheAlertDialog(
|
||||
dialogTitle = dialogTitle,
|
||||
onClickCancel = { isDialogVisible = false },
|
||||
onClick = dialogOnClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF)
|
||||
@Composable
|
||||
private fun CachePreferencesComponentPreview() {
|
||||
AppTheme {
|
||||
Scaffold { padding ->
|
||||
CachePreferencesComponent(
|
||||
recaptchaCookiesEnabled = false,
|
||||
onEvent = {},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(padding)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CacheAlertDialog(
|
||||
dialogTitle: String,
|
||||
onClickCancel: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onClickCancel,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onClick) {
|
||||
Text(text = "Delete")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onClickCancel) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = dialogTitle)
|
||||
},
|
||||
text = {
|
||||
Text(text = "This is an irreversible action")
|
||||
},
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(backgroundColor = 0xFFFFFFFF)
|
||||
@Composable
|
||||
private fun CacheAlertDialogPreview() {
|
||||
AppTheme {
|
||||
Scaffold { padding ->
|
||||
CacheAlertDialog(
|
||||
dialogTitle = "Delete view history",
|
||||
onClickCancel = {},
|
||||
onClick = {},
|
||||
modifier = Modifier.padding(padding)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.settings.components.switch_preference.SwitchPreferenceComponent
|
||||
import org.schabi.newpipe.settings.presentation.history_cache.state.SwitchPreferencesUiState
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun HistoryPreferencesComponent(
|
||||
state: SwitchPreferencesUiState,
|
||||
onEvent: (Int, Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
SwitchPreferenceComponent(
|
||||
title = stringResource(id = R.string.enable_watch_history_title),
|
||||
summary = stringResource(id = R.string.enable_watch_history_summary),
|
||||
isChecked = state.watchHistoryEnabled,
|
||||
onCheckedChange = {
|
||||
onEvent(R.string.enable_watch_history_key, it)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
SwitchPreferenceComponent(
|
||||
title = stringResource(id = R.string.enable_playback_resume_title),
|
||||
summary = stringResource(id = R.string.enable_playback_resume_summary),
|
||||
isChecked = state.resumePlaybackEnabled,
|
||||
onCheckedChange = {
|
||||
onEvent(R.string.enable_playback_resume_key, it)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
SwitchPreferenceComponent(
|
||||
title = stringResource(id = R.string.enable_playback_state_lists_title),
|
||||
summary = stringResource(id = R.string.enable_playback_state_lists_summary),
|
||||
isChecked = state.positionsInListsEnabled,
|
||||
onCheckedChange = {
|
||||
onEvent(R.string.enable_playback_state_lists_key, it)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
SwitchPreferenceComponent(
|
||||
title = stringResource(id = R.string.enable_search_history_title),
|
||||
summary = stringResource(id = R.string.enable_search_history_summary),
|
||||
isChecked = state.searchHistoryEnabled,
|
||||
onCheckedChange = {
|
||||
onEvent(R.string.enable_search_history_key, it)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SwitchPreferencesComponentPreview() {
|
||||
var state by remember {
|
||||
mutableStateOf(
|
||||
SwitchPreferencesUiState()
|
||||
)
|
||||
}
|
||||
AppTheme(
|
||||
useDarkTheme = false
|
||||
) {
|
||||
Scaffold { padding ->
|
||||
HistoryPreferencesComponent(
|
||||
state = state,
|
||||
onEvent = { _, _ ->
|
||||
// Mock behaviour to preview
|
||||
state = state.copy(
|
||||
watchHistoryEnabled = !state.watchHistoryEnabled
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(padding),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache.events
|
||||
|
||||
sealed class HistoryCacheEvent {
|
||||
data class OnUpdateBooleanPreference(val key: Int, val isEnabled: Boolean) : HistoryCacheEvent()
|
||||
data class OnClickWipeCachedMetadata(val key: Int) : HistoryCacheEvent()
|
||||
data class OnClickClearWatchHistory(val key: Int) : HistoryCacheEvent()
|
||||
data class OnClickDeletePlaybackPositions(val key: Int) : HistoryCacheEvent()
|
||||
data class OnClickClearSearchHistory(val key: Int) : HistoryCacheEvent()
|
||||
data class OnClickReCaptchaCookies(val key: Int) : HistoryCacheEvent()
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache.events
|
||||
|
||||
sealed class HistoryCacheUiEvent {
|
||||
data object ShowDeletePlaybackSnackbar : HistoryCacheUiEvent()
|
||||
data object ShowDeleteSearchHistorySnackbar : HistoryCacheUiEvent()
|
||||
data object ShowClearWatchHistorySnackbar : HistoryCacheUiEvent()
|
||||
data object ShowReCaptchaCookiesSnackbar : HistoryCacheUiEvent()
|
||||
data object ShowWipeCachedMetadataSnackbar : HistoryCacheUiEvent()
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.schabi.newpipe.settings.presentation.history_cache.state
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
@Stable
|
||||
data class SwitchPreferencesUiState(
|
||||
val watchHistoryEnabled: Boolean = false,
|
||||
val resumePlaybackEnabled: Boolean = false,
|
||||
val positionsInListsEnabled: Boolean = false,
|
||||
val searchHistoryEnabled: Boolean = false,
|
||||
)
|
@ -23,7 +23,7 @@
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.HistorySettingsFragment"
|
||||
android:fragment="org.schabi.newpipe.settings.presentation.history_cache.HistoryCacheFragment"
|
||||
android:icon="@drawable/ic_history"
|
||||
android:title="@string/settings_category_history_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
Loading…
Reference in New Issue
Block a user