Start implementing copying multireddits.

This commit is contained in:
Docile-Alligator
2025-11-28 14:55:40 -05:00
parent ddd038f22f
commit b6ca992bcb
15 changed files with 265 additions and 30 deletions

View File

@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.kapt'
}
android {
@ -54,8 +55,14 @@ android {
buildFeatures {
buildConfig = true
viewBinding = true
compose = true
}
namespace 'ml.docilealligator.infinityforreddit'
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
kotlinOptions {
jvmTarget = '11'
}
@ -86,7 +93,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.4.0'
def roomVersion = "2.6.1"
implementation "androidx.room:room-runtime:$roomVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
implementation "androidx.room:room-guava:$roomVersion"
implementation "androidx.room:room-ktx:$roomVersion"
implementation 'androidx.viewpager2:viewpager2:1.1.0'
@ -117,7 +124,7 @@ dependencies {
// Dependency injection
def daggerVersion = '2.51.1'
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
// Binding
compileOnly 'com.android.databinding:viewbinding:8.5.1'
@ -125,7 +132,7 @@ dependencies {
// Events
def eventbusVersion = "3.3.1"
implementation "org.greenrobot:eventbus:$eventbusVersion"
annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbusVersion"
kapt "org.greenrobot:eventbus-annotation-processor:$eventbusVersion"
// TransactionTooLargeException avoidance
implementation 'com.github.livefront:bridge:v2.0.2'
@ -134,7 +141,7 @@ dependencies {
// NOTE: Deprecated
def stateVersion = "1.4.1"
implementation "com.evernote:android-state:$stateVersion"
annotationProcessor "com.evernote:android-state-processor:$stateVersion"
kapt "com.evernote:android-state-processor:$stateVersion"
// Object to JSON
// NOTE: Replace with Squareup's Moshi?
@ -155,7 +162,7 @@ dependencies {
//Image loading
def glideVersion = "5.0.5"
implementation "com.github.bumptech.glide:glide:$glideVersion"
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
kapt "com.github.bumptech.glide:compiler:$glideVersion"
implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.santalu:aspect-ratio-imageview:1.0.9'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.29'
@ -203,6 +210,19 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
// Compose
def composeBom = platform('androidx.compose:compose-bom:2025.11.01')
implementation composeBom
androidTestImplementation composeBom
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.ui:ui-tooling-preview'
debugImplementation 'androidx.compose.ui:ui-tooling'
//implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.activity:activity-compose:1.10.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.9.4'
implementation 'androidx.compose.runtime:runtime-livedata'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
/**** Builds and flavors ****/
// debugImplementation because LeakCanary should only run in debug builds.
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:x.y'

View File

@ -28,22 +28,24 @@
<application
android:name=".Infinity"
android:allowBackup="false"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/application_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
android:enableOnBackInvokedCallback="true"
tools:replace="android:label">
<activity
android:name=".activities.CopyMultiRedditActivity"
android:exported="false" />
<activity
android:name=".activities.LoginChromeCustomTabActivity"
android:exported="true"
android:label="@string/login_activity_label"
android:parentActivityName=".activities.MainActivity"
android:theme="@style/AppTheme.NoActionBar"
android:launchMode="singleTop"
android:exported="true">
android:parentActivityName=".activities.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -54,7 +56,6 @@
android:host="localhost"
android:scheme="infinity" />
</intent-filter>
</activity>
<activity
android:name=".activities.CommentFilterUsageListingActivity"
@ -158,8 +159,8 @@
<service
android:name=".services.DownloadMediaService"
android:enabled="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<activity
android:name=".activities.ViewRedditGalleryActivity"
@ -176,8 +177,8 @@
<service
android:name=".services.DownloadRedditVideoService"
android:enabled="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<activity
android:name=".activities.ViewPrivateMessagesActivity"
@ -456,13 +457,13 @@
<service
android:name=".services.SubmitPostService"
android:enabled="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service
android:name=".services.EditProfileService"
android:enabled="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<receiver android:name=".broadcastreceivers.DownloadedMediaDeleteActionBroadcastReceiver" />
</application>

View File

@ -11,6 +11,7 @@ import ml.docilealligator.infinityforreddit.activities.AccountSavedThingActivity
import ml.docilealligator.infinityforreddit.activities.CommentActivity;
import ml.docilealligator.infinityforreddit.activities.CommentFilterPreferenceActivity;
import ml.docilealligator.infinityforreddit.activities.CommentFilterUsageListingActivity;
import ml.docilealligator.infinityforreddit.activities.CopyMultiRedditActivity;
import ml.docilealligator.infinityforreddit.activities.CreateMultiRedditActivity;
import ml.docilealligator.infinityforreddit.activities.CustomThemeListingActivity;
import ml.docilealligator.infinityforreddit.activities.CustomThemePreviewActivity;
@ -318,6 +319,8 @@ public interface AppComponent {
void inject(ProxyPreferenceFragment proxyPreferenceFragment);
void inject(CopyMultiRedditActivity copyMultiRedditActivity);
@Component.Factory
interface Factory {
AppComponent create(@BindsInstance Application application);

View File

@ -60,6 +60,8 @@ public class Infinity extends Application implements LifecycleObserver {
private boolean canStartLockScreenActivity = false;
private boolean isSecureMode;
@Inject
public RedditDataRoomDatabase mRedditDataRoomDatabase;
@Inject
@Named("default")
SharedPreferences mSharedPreferences;
@Inject

View File

@ -0,0 +1,85 @@
package ml.docilealligator.infinityforreddit.activities
import android.content.SharedPreferences
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import ml.docilealligator.infinityforreddit.Infinity
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper
import ml.docilealligator.infinityforreddit.customviews.compose.AppTheme
import retrofit2.Retrofit
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Named
@OptIn(ExperimentalMaterial3Api::class)
class CopyMultiRedditActivity : BaseActivity() {
@Inject
@Named("oauth")
lateinit var mOauthRetrofit: Retrofit
@Inject
lateinit var mRedditDataRoomDatabase: RedditDataRoomDatabase
@Inject
@Named("default")
lateinit var mSharedPreferences: SharedPreferences
@Inject
@Named("current_account")
lateinit var mCurrentAccountSharedPreferences: SharedPreferences
@Inject
lateinit var mCustomThemeWrapper: CustomThemeWrapper
@Inject
lateinit var mExecutor: Executor
override fun onCreate(savedInstanceState: Bundle?) {
((application) as Infinity).appComponent.inject(this)
super.onCreate(savedInstanceState)
setContent {
AppTheme(customThemeWrapper.themeType) {
Scaffold(
topBar = {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text("Small Top App Bar")
}
)
},
) { innerPadding ->
}
}
}
}
override fun getDefaultSharedPreferences(): SharedPreferences? {
return mSharedPreferences
}
override fun getCurrentAccountSharedPreferences(): SharedPreferences? {
return mCurrentAccountSharedPreferences
}
override fun getCustomThemeWrapper(): CustomThemeWrapper? {
return mCustomThemeWrapper
}
override fun applyCustomTheme() {
}
}

View File

@ -50,7 +50,6 @@ public class LinkResolverActivity extends AppCompatActivity {
private static final String SHARELINK_SUBREDDIT_PATTERN = "/r/[\\w-]+/s/[\\w-]+";
private static final String SHARELINK_USER_PATTERN = "/u/[\\w-]+/s/[\\w-]+";
private static final String SIDEBAR_PATTERN = "/[rR]/[\\w-]+/about/sidebar";
private static final String MULTIREDDIT_PATTERN = "/user/[\\w-]+/m/\\w+/?";
private static final String MULTIREDDIT_PATTERN_2 = "/[rR]/(\\w+\\+?)+/?";
private static final String REDD_IT_POST_PATTERN = "/\\w+/?";
private static final String REDGIFS_PATTERN = "/watch/[\\w-]+$";
@ -201,6 +200,11 @@ public class LinkResolverActivity extends AppCompatActivity {
} catch (Exception e) {
deepLinkError(uri);
}
} else if(segments.size() == 4 && segments.get(0).equals("user") && segments.get(2).equals("m")) {
// Multireddit
Intent intent = new Intent(this, ViewMultiRedditDetailActivity.class);
intent.putExtra(ViewMultiRedditDetailActivity.EXTRA_MULTIREDDIT_PATH, path);
startActivity(intent);
} else if (path.matches(POST_PATTERN) || path.matches(POST_PATTERN_2)) {
int commentsIndex = segments.lastIndexOf("comments");
if (commentsIndex >= 0 && commentsIndex < segments.size() - 1) {
@ -263,10 +267,6 @@ public class LinkResolverActivity extends AppCompatActivity {
intent.putExtra(ViewSubredditDetailActivity.EXTRA_SUBREDDIT_NAME_KEY, path.substring(3, path.length() - 14));
intent.putExtra(ViewSubredditDetailActivity.EXTRA_VIEW_SIDEBAR, true);
startActivity(intent);
} else if (path.matches(MULTIREDDIT_PATTERN)) {
Intent intent = new Intent(this, ViewMultiRedditDetailActivity.class);
intent.putExtra(ViewMultiRedditDetailActivity.EXTRA_MULTIREDDIT_PATH, path);
startActivity(intent);
} else if (path.matches(MULTIREDDIT_PATTERN_2)) {
String subredditName = path.substring(3);
Intent intent = new Intent(this, ViewSubredditDetailActivity.class);

View File

@ -116,9 +116,6 @@ public class ViewMultiRedditDetailActivity extends BaseActivity implements SortT
@Named("current_account")
SharedPreferences mCurrentAccountSharedPreferences;
@Inject
@Named("bottom_app_bar")
SharedPreferences bottomAppBarSharedPreference;
@Inject
@Named("nsfw_and_spoiler")
SharedPreferences mNsfwAndSpoilerSharedPreferences;
@Inject
@ -383,7 +380,7 @@ public class ViewMultiRedditDetailActivity extends BaseActivity implements SortT
navigationWrapper.floatingActionButton.setLayoutParams(lp);
}
fabOption = bottomAppBarSharedPreference.getInt(SharedPreferencesUtils.OTHER_ACTIVITIES_BOTTOM_APP_BAR_FAB, SharedPreferencesUtils.OTHER_ACTIVITIES_BOTTOM_APP_BAR_FAB_SUBMIT_POSTS);
fabOption = mBottomAppBarSharedPreference.getInt(SharedPreferencesUtils.OTHER_ACTIVITIES_BOTTOM_APP_BAR_FAB, SharedPreferencesUtils.OTHER_ACTIVITIES_BOTTOM_APP_BAR_FAB_SUBMIT_POSTS);
switch (fabOption) {
case SharedPreferencesUtils.OTHER_ACTIVITIES_BOTTOM_APP_BAR_FAB_REFRESH:
navigationWrapper.floatingActionButton.setImageResource(R.drawable.ic_refresh_day_night_24dp);
@ -717,7 +714,7 @@ public class ViewMultiRedditDetailActivity extends BaseActivity implements SortT
autoCompleteRunnable = () -> {
subredditAutocompleteCall = mOauthRetrofit.create(RedditAPI.class).subredditAutocomplete(APIUtils.getOAuthHeader(accessToken),
currentQuery, nsfw);
subredditAutocompleteCall.enqueue(new Callback<String>() {
subredditAutocompleteCall.enqueue(new Callback<>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
subredditAutocompleteCall = null;
@ -812,6 +809,14 @@ public class ViewMultiRedditDetailActivity extends BaseActivity implements SortT
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.view_multi_reddit_detail_activity, menu);
if (multiReddit == null && multiPath != null) {
String[] segments = multiPath.split("/");
if (segments.length > 2 && !segments[1].equals(accountName)) {
menu.findItem(R.id.action_edit_view_multi_reddit_detail_activity).setVisible(false);
menu.findItem(R.id.action_delete_view_multi_reddit_detail_activity).setVisible(false);
menu.findItem(R.id.action_copy_view_multi_reddit_detail_activity).setVisible(true);
}
}
applyMenuItemTheme(menu);
return true;
}
@ -882,6 +887,10 @@ public class ViewMultiRedditDetailActivity extends BaseActivity implements SortT
.setNegativeButton(R.string.cancel, null)
.show();
return true;
} else if (itemId == R.id.action_copy_view_multi_reddit_detail_activity) {
Intent intent = new Intent(this, CopyMultiRedditActivity.class);
startActivity(intent);
return true;
}
return false;
}

View File

@ -8,6 +8,8 @@ import androidx.room.Query;
import java.util.List;
import kotlinx.coroutines.flow.Flow;
@Dao
public interface CustomThemeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
@ -40,6 +42,15 @@ public interface CustomThemeDao {
@Query("SELECT * FROM custom_themes WHERE is_amoled_theme = 1 LIMIT 1")
LiveData<CustomTheme> getAmoledCustomThemeLiveData();
@Query("SELECT * FROM custom_themes WHERE is_light_theme = 1 LIMIT 1")
Flow<CustomTheme> getLightCustomThemeFlow();
@Query("SELECT * FROM custom_themes WHERE is_dark_theme = 1 LIMIT 1")
Flow<CustomTheme> getDarkCustomThemeFlow();
@Query("SELECT * FROM custom_themes WHERE is_amoled_theme = 1 LIMIT 1")
Flow<CustomTheme> getAmoledCustomThemeFlow();
@Query("SELECT * FROM custom_themes WHERE name = :name COLLATE NOCASE LIMIT 1")
CustomTheme getCustomTheme(String name);

View File

@ -48,6 +48,10 @@ public class CustomThemeWrapper {
}
}
public int getThemeType() {
return themeType;
}
public void setThemeType(int themeType) {
this.themeType = themeType;
}

View File

@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData;
import java.util.List;
import kotlinx.coroutines.flow.Flow;
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase;
public class LocalCustomThemeRepository {
@ -12,11 +13,19 @@ public class LocalCustomThemeRepository {
private final LiveData<CustomTheme> mCurrentDarkCustomTheme;
private final LiveData<CustomTheme> mCurrentAmoledCustomTheme;
LocalCustomThemeRepository(RedditDataRoomDatabase redditDataRoomDatabase) {
private final Flow<CustomTheme> mCurrentLightCustomThemeFlow;
private final Flow<CustomTheme> mCurrentDarkCustomThemeFlow;
private final Flow<CustomTheme> mCurrentAmoledCustomThemeFlow;
public LocalCustomThemeRepository(RedditDataRoomDatabase redditDataRoomDatabase) {
mAllCustomThemes = redditDataRoomDatabase.customThemeDao().getAllCustomThemes();
mCurrentLightCustomTheme = redditDataRoomDatabase.customThemeDao().getLightCustomThemeLiveData();
mCurrentDarkCustomTheme = redditDataRoomDatabase.customThemeDao().getDarkCustomThemeLiveData();
mCurrentAmoledCustomTheme = redditDataRoomDatabase.customThemeDao().getAmoledCustomThemeLiveData();
mCurrentLightCustomThemeFlow = redditDataRoomDatabase.customThemeDao().getLightCustomThemeFlow();
mCurrentDarkCustomThemeFlow = redditDataRoomDatabase.customThemeDao().getDarkCustomThemeFlow();
mCurrentAmoledCustomThemeFlow = redditDataRoomDatabase.customThemeDao().getAmoledCustomThemeFlow();
}
LiveData<List<CustomTheme>> getAllCustomThemes() {
@ -34,4 +43,16 @@ public class LocalCustomThemeRepository {
LiveData<CustomTheme> getCurrentAmoledCustomTheme() {
return mCurrentAmoledCustomTheme;
}
public Flow<CustomTheme> getCurrentLightCustomThemeFlow() {
return mCurrentLightCustomThemeFlow;
}
public Flow<CustomTheme> getCurrentDarkCustomThemeFlow() {
return mCurrentDarkCustomThemeFlow;
}
public Flow<CustomTheme> getCurrentAmoledCustomThemeFlow() {
return mCurrentAmoledCustomThemeFlow;
}
}

View File

@ -0,0 +1,54 @@
package ml.docilealligator.infinityforreddit.customviews.compose
import android.content.Context
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.flow.map
import ml.docilealligator.infinityforreddit.Infinity
import ml.docilealligator.infinityforreddit.customtheme.CustomTheme
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper
import ml.docilealligator.infinityforreddit.customtheme.LocalCustomThemeRepository
import ml.docilealligator.infinityforreddit.utils.CustomThemeSharedPreferencesUtils
val LocalAppTheme = staticCompositionLocalOf<CustomTheme> {
error("No default theme values")
}
@Composable
fun AppTheme(themeType: Int, content: @Composable () -> Unit) {
val context = LocalContext.current
val localCustomThemeRepository = LocalCustomThemeRepository(((context.applicationContext) as Infinity).mRedditDataRoomDatabase)
val currentThemeFlow = when(themeType) {
CustomThemeSharedPreferencesUtils.LIGHT -> localCustomThemeRepository.currentLightCustomThemeFlow
CustomThemeSharedPreferencesUtils.DARK -> localCustomThemeRepository.currentDarkCustomThemeFlow
CustomThemeSharedPreferencesUtils.AMOLED -> localCustomThemeRepository.currentAmoledCustomThemeFlow
else -> localCustomThemeRepository.currentLightCustomThemeFlow
}
val customTheme by currentThemeFlow.map {
it ?: getDefaultTheme(context, themeType)
}.collectAsState(
getDefaultTheme(context, themeType)
)
CompositionLocalProvider(LocalAppTheme provides customTheme) {
MaterialTheme {
content()
}
}
}
private fun getDefaultTheme(context: Context, themeType: Int): CustomTheme {
return when(themeType) {
CustomThemeSharedPreferencesUtils.LIGHT -> CustomThemeWrapper.getIndigo(context)
CustomThemeSharedPreferencesUtils.DARK -> CustomThemeWrapper.getIndigoDark(context)
CustomThemeSharedPreferencesUtils.AMOLED -> CustomThemeWrapper.getIndigoAmoled(context)
else -> CustomThemeWrapper.getIndigo(context)
}
}

View File

@ -0,0 +1,8 @@
package ml.docilealligator.infinityforreddit.repositories
import retrofit2.Retrofit
class CopyMultiRedditActivityRepository(
oauthRetrofit: Retrofit
) {
}

View File

@ -0,0 +1,9 @@
package ml.docilealligator.infinityforreddit.viewmodels
import androidx.lifecycle.ViewModel
import ml.docilealligator.infinityforreddit.repositories.CopyMultiRedditActivityRepository
class CopyMultiRedditActivityViewModel(
copyMultiRedditActivityRepository: CopyMultiRedditActivityRepository
): ViewModel() {
}

View File

@ -39,4 +39,11 @@
android:orderInCategory="6"
android:title="@string/action_delete_multi_reddit"
app:showAsAction="never" />
<item
android:id="@+id/action_copy_view_multi_reddit_detail_activity"
android:orderInCategory="6"
android:title="@string/action_copy_multi_reddit"
android:visible="false"
app:showAsAction="never" />
</menu>

View File

@ -74,6 +74,7 @@
<string name="action_save">Save</string>
<string name="action_edit_multi_reddit">Edit MultiReddit</string>
<string name="action_delete_multi_reddit">Delete Multireddit</string>
<string name="action_copy_multi_reddit">Copy Multireddit</string>
<string name="action_share">Share</string>
<string name="action_preview">Preview</string>
<string name="action_report">Report</string>