From 5ef543c69b77b694873fa117fbc3eb3e0bda4326 Mon Sep 17 00:00:00 2001 From: Docile-Alligator <25734209+Docile-Alligator@users.noreply.github.com> Date: Thu, 14 Nov 2024 23:44:03 -0500 Subject: [PATCH] Start implementing modmail using Kotlin + Compose. --- app/build.gradle | 50 +++++ app/src/main/AndroidManifest.xml | 31 +-- .../infinityforreddit/AppComponent.java | 3 + .../activities/MainActivity.java | 3 + .../activities/ModmailActivity.kt | 179 ++++++++++++++++++ .../infinityforreddit/apis/RedditAPIKt.kt | 15 ++ .../infinityforreddit/mod/Author.kt | 18 ++ .../infinityforreddit/mod/Conversation.kt | 55 ++++++ .../infinityforreddit/mod/ModMail.kt | 42 ++++ .../mod/ModMailConversationPagingSource.kt | 73 +++++++ .../mod/ModMailConversationViewModel.kt | 40 ++++ .../infinityforreddit/mod/ModMessage.kt | 13 ++ .../infinityforreddit/utils/JSONUtils.java | 5 + app/src/main/res/values/strings.xml | 3 + build.gradle | 2 + 15 files changed, 518 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/activities/ModmailActivity.kt create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPIKt.kt create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/mod/Author.kt create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/mod/Conversation.kt create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMail.kt create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationPagingSource.kt create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationViewModel.kt create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMessage.kt diff --git a/app/build.gradle b/app/build.gradle index 7fe0023e..600781fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,9 @@ plugins { id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-parcelize' + id 'kotlin-kapt' + id 'com.google.devtools.ksp' } android { @@ -39,6 +43,18 @@ android { targetCompatibility JavaVersion.VERSION_11 } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.14" + } + + kotlinOptions { + jvmTarget = '11' + } + + kapt { + correctErrorTypes = true + } + lint { disable 'MissingTranslation' } @@ -52,6 +68,7 @@ android { buildFeatures { buildConfig = true viewBinding = true + compose = true } namespace 'ml.docilealligator.infinityforreddit' } @@ -63,6 +80,7 @@ dependencies { implementation 'androidx.browser:browser:1.8.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1" implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.activity:activity:1.9.0' def lifecycleVersion = "2.7.0" @@ -75,11 +93,13 @@ dependencies { def pagingVersion = '3.3.0' implementation "androidx.paging:paging-runtime:$pagingVersion" implementation "androidx.paging:paging-guava:$pagingVersion" + implementation "androidx.paging:paging-compose:3.3.4" implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.3.2' 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.viewpager2:viewpager2:1.1.0' implementation 'androidx.work:work-runtime:2.9.0' @@ -108,6 +128,7 @@ dependencies { def daggerVersion = '2.51.1' implementation "com.google.dagger:dagger:$daggerVersion" annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" + ksp "com.google.dagger:dagger-compiler:$daggerVersion" // Binding compileOnly 'com.android.databinding:viewbinding:8.5.1' @@ -116,6 +137,7 @@ dependencies { 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' @@ -125,6 +147,7 @@ dependencies { 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? @@ -146,6 +169,7 @@ dependencies { def glideVersion = "4.16.0" implementation "com.github.bumptech.glide:glide:$glideVersion" annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion" + ksp "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' @@ -193,6 +217,32 @@ dependencies { implementation 'com.giphy.sdk:ui:2.3.15' + // Compose + def composeBom = platform('androidx.compose:compose-bom:2024.10.01') + implementation composeBom + androidTestImplementation composeBom + + // Choose one of the following: + // Material Design 3 + implementation 'androidx.compose.material3:material3' + implementation("androidx.compose.material3.adaptive:adaptive") + implementation("androidx.compose.material3.adaptive:adaptive-layout") + implementation("androidx.compose.material3.adaptive:adaptive-navigation") + + // Android Studio Preview support + implementation 'androidx.compose.ui:ui-tooling-preview' + debugImplementation 'androidx.compose.ui:ui-tooling' + + // UI Tests + androidTestImplementation 'androidx.compose.ui:ui-test-junit4' + debugImplementation 'androidx.compose.ui:ui-test-manifest' + + // Optional - Integration with activities + implementation 'androidx.activity:activity-compose:1.9.3' + // Optional - Integration with ViewModels + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7' + // Optional - Integration with LiveData + implementation 'androidx.compose.runtime:runtime-livedata' /**** Builds and flavors ****/ // debugImplementation because LeakCanary should only run in debug builds. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 544c0907..9a60660a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,13 +36,17 @@ android:usesCleartextTraffic="true" tools:replace="android:label"> + - + android:parentActivityName=".activities.MainActivity" + android:theme="@style/AppTheme.Slidable"> @@ -53,7 +57,6 @@ android:host="localhost" android:scheme="infinity" /> - + android:exported="false" + android:permission="android.permission.BIND_JOB_SERVICE" /> + android:exported="false" + android:permission="android.permission.BIND_JOB_SERVICE" /> + android:exported="false" + android:permission="android.permission.BIND_JOB_SERVICE" /> + android:exported="false" + android:permission="android.permission.BIND_JOB_SERVICE" /> diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index db5c9cee..9318c1e1 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -31,6 +31,7 @@ import ml.docilealligator.infinityforreddit.activities.LockScreenActivity; import ml.docilealligator.infinityforreddit.activities.LoginActivity; import ml.docilealligator.infinityforreddit.activities.LoginChromeCustomTabActivity; import ml.docilealligator.infinityforreddit.activities.MainActivity; +import ml.docilealligator.infinityforreddit.activities.ModmailActivity; import ml.docilealligator.infinityforreddit.activities.PostFilterPreferenceActivity; import ml.docilealligator.infinityforreddit.activities.PostFilterUsageListingActivity; import ml.docilealligator.infinityforreddit.activities.PostGalleryActivity; @@ -312,6 +313,8 @@ public interface AppComponent { void inject(LoginChromeCustomTabActivity loginChromeCustomTabActivity); + void inject(ModmailActivity modMailActivity); + @Component.Factory interface Factory { AppComponent create(@BindsInstance Application application); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java index 5bf73d83..f91d96c2 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/MainActivity.java @@ -316,6 +316,9 @@ public class MainActivity extends BaseActivity implements SortTypeSelectionCallb }*/ initializeNotificationAndBindView(); + + Intent intent = new Intent(this, ModmailActivity.class); + startActivity(intent); } @Override diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ModmailActivity.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ModmailActivity.kt new file mode 100644 index 00000000..0531034f --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ModmailActivity.kt @@ -0,0 +1,179 @@ +package ml.docilealligator.infinityforreddit.activities + +import android.content.SharedPreferences +import android.os.Bundle +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults.topAppBarColors +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold +import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModelProvider +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import ml.docilealligator.infinityforreddit.Infinity +import ml.docilealligator.infinityforreddit.R +import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper +import ml.docilealligator.infinityforreddit.mod.Conversation +import ml.docilealligator.infinityforreddit.mod.ModMailConversationViewModel +import ml.docilealligator.infinityforreddit.mod.ModMessage +import retrofit2.Retrofit +import javax.inject.Inject +import javax.inject.Named + +@OptIn(ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterial3Api::class) +class ModmailActivity : BaseActivity() { + @Inject + @Named("oauth") + lateinit var mOauthRetrofit: Retrofit + @Inject + @Named("default") + lateinit var mSharedPreferences: SharedPreferences + @Inject + @Named("current_account") + lateinit var mCurrentAccountSharedPreferences: SharedPreferences + @Inject + lateinit var mCustomThemeWrapper: CustomThemeWrapper + + lateinit var conversationViewModel: ModMailConversationViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + (application as Infinity).appComponent.inject(this) + + super.onCreate(savedInstanceState) + + if (accessToken == null) { + Toast.makeText(this, R.string.login_first, Toast.LENGTH_SHORT).show() + finish() + return + } + + enableEdgeToEdge() + + conversationViewModel = ViewModelProvider.create(this, ModMailConversationViewModel.Factory(mOauthRetrofit, accessToken!!, mSharedPreferences))[ModMailConversationViewModel::class] + + setContent { + Scaffold( + topBar = { + TopAppBar( + colors = topAppBarColors( + containerColor = Color(mCustomThemeWrapper.colorPrimary), + titleContentColor = Color(mCustomThemeWrapper.toolbarPrimaryTextAndIconColor) + ), + title = { + Text(getString(R.string.modmail_activity_label)) + } + ) + } + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + val navigator = rememberListDetailPaneScaffoldNavigator() + + BackHandler(navigator.canNavigateBack()) { + navigator.navigateBack() + } + + ListDetailPaneScaffold( + modifier = Modifier.padding(16.dp), + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + listPane = { + AnimatedPane { + ConversationListView(conversationViewModel.flow.collectAsLazyPagingItems()) + } + }, + detailPane = { + AnimatedPane { + navigator.currentDestination?.content?.let { + ConversationDetailsView(it) + } + } + } + ) + } + } + } + } + + override fun getDefaultSharedPreferences(): SharedPreferences { + return mSharedPreferences + } + + override fun getCurrentAccountSharedPreferences(): SharedPreferences { + return mCurrentAccountSharedPreferences + } + + override fun getCustomThemeWrapper(): CustomThemeWrapper { + return mCustomThemeWrapper + } + + override fun applyCustomTheme() { + + } + + @Composable + fun ConversationListView(pagingItems: LazyPagingItems) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(count = pagingItems.itemCount) { index: Int -> + val conversation = pagingItems[index] + conversation?.let { + ConversationView(it) + } + } + } + } + + @Composable + fun ConversationView(conversation: Conversation) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + conversation.owner?.displayName?.let { + Text(text = it, color = Color(mCustomThemeWrapper.subreddit)) + } + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + for (author in conversation.authors) { + author.name?.let { + Text(text = it, color = Color(mCustomThemeWrapper.username)) + } + } + } + conversation.subject?.let { + Text(text = it) + } + } + } + + @Composable + fun ConversationDetailsView(conversation: Conversation) { + + } + + @Composable + fun MessageView(message: ModMessage) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPIKt.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPIKt.kt new file mode 100644 index 00000000..4cab2d19 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPIKt.kt @@ -0,0 +1,15 @@ +package ml.docilealligator.infinityforreddit.apis + +import retrofit2.Call +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.HeaderMap +import retrofit2.http.Query + +interface RedditAPIKt { + @GET("/api/mod/conversations") + suspend fun getModMailConversations( + @HeaderMap headers: Map, + @Query("after") after: String? + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/mod/Author.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/Author.kt new file mode 100644 index 00000000..b046e0d1 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/Author.kt @@ -0,0 +1,18 @@ +package ml.docilealligator.infinityforreddit.mod + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Author( + @SerializedName("isMod") var isMod: Boolean? = null, + @SerializedName("isAdmin") var isAdmin: Boolean? = null, + @SerializedName("name") var name: String? = null, + @SerializedName("isOp") var isOp: Boolean? = null, + @SerializedName("isParticipant") var isParticipant: Boolean? = null, + @SerializedName("isApproved") var isApproved: Boolean? = null, + @SerializedName("isHidden") var isHidden: Boolean? = null, + @SerializedName("id") var id: String? = null, + @SerializedName("isDeleted") var isDeleted: Boolean? = null +) : Parcelable diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/mod/Conversation.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/Conversation.kt new file mode 100644 index 00000000..87a4db56 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/Conversation.kt @@ -0,0 +1,55 @@ +package ml.docilealligator.infinityforreddit.mod + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Conversation( + @SerializedName("isAuto") var isAuto: Boolean? = null, + @SerializedName("participant") var participant: Participant? = Participant(), + @SerializedName("objIds") var objIds: ArrayList = arrayListOf(), + @SerializedName("isRepliable") var isRepliable: Boolean? = null, + @SerializedName("lastUserUpdate") var lastUserUpdate: String? = null, + @SerializedName("isInternal") var isInternal: Boolean? = null, + @SerializedName("lastModUpdate") var lastModUpdate: String? = null, + @SerializedName("authors") var authors: ArrayList = arrayListOf(), + @SerializedName("lastUpdated") var lastUpdated: String? = null, + @SerializedName("legacyFirstMessageId") var legacyFirstMessageId: String? = null, + @SerializedName("state") var state: Int? = null, + @SerializedName("conversationType") var conversationType: String? = null, + @SerializedName("lastUnread") var lastUnread: String? = null, + @SerializedName("owner") var owner: Owner? = Owner(), + @SerializedName("subject") var subject: String? = null, + @SerializedName("id") var id: String? = null, + @SerializedName("isHighlighted") var isHighlighted: Boolean? = null, + @SerializedName("numMessages") var numMessages: Int? = null +): Parcelable { + val messages: MutableList = mutableListOf() +} + +@Parcelize +data class Participant( + @SerializedName("isMod") var isMod: Boolean? = null, + @SerializedName("isAdmin") var isAdmin: Boolean? = null, + @SerializedName("name") var name: String? = null, + @SerializedName("isOp") var isOp: Boolean? = null, + @SerializedName("isParticipant") var isParticipant: Boolean? = null, + @SerializedName("isApproved") var isApproved: Boolean? = null, + @SerializedName("isHidden") var isHidden: Boolean? = null, + @SerializedName("id") var id: String? = null, + @SerializedName("isDeleted") var isDeleted: Boolean? = null +): Parcelable + +@Parcelize +data class ObjId( + @SerializedName("id") var id: String? = null, + @SerializedName("key") var key: String? = null +): Parcelable + +@Parcelize +data class Owner( + @SerializedName("displayName") var displayName: String? = null, + @SerializedName("type") var type: String? = null, + @SerializedName("id") var id: String? = null +): Parcelable diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMail.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMail.kt new file mode 100644 index 00000000..2989220a --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMail.kt @@ -0,0 +1,42 @@ +package ml.docilealligator.infinityforreddit.mod + +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import org.json.JSONObject +import java.io.IOException + +data class ModMail( + @SerializedName("viewerId") var viewerId: String? = null +) { + lateinit var conversations: MutableList; + lateinit var messages: MutableList; + val conversationIds: MutableList = arrayListOf() + + fun parseConversations(conversationsJSONObject: JSONObject, gson: Gson) { + for (conversationId in conversationIds) { + try { + conversations.add(gson.fromJson(conversationsJSONObject.getString(conversationId), Conversation::class.java)) + } catch (ignore: IOException) { + ignore.printStackTrace() + } + } + } + + fun parseModMessages(messagesJSONObject: JSONObject, gson: Gson) { + for (conversation in conversations) { + for (objId in conversation.objIds) { + objId.key?.let { key -> + if (key == "messages") { + objId.id?.let { id -> + try { + messages.add(gson.fromJson(messagesJSONObject.getString(id), ModMessage::class.java)) + } catch (ignore: IOException) { + ignore.printStackTrace() + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationPagingSource.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationPagingSource.kt new file mode 100644 index 00000000..9e4a762e --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationPagingSource.kt @@ -0,0 +1,73 @@ +package ml.docilealligator.infinityforreddit.mod + +import android.content.SharedPreferences +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ml.docilealligator.infinityforreddit.apis.RedditAPI +import ml.docilealligator.infinityforreddit.apis.RedditAPIKt +import ml.docilealligator.infinityforreddit.utils.APIUtils +import ml.docilealligator.infinityforreddit.utils.JSONUtils +import org.json.JSONObject +import retrofit2.Retrofit +import java.io.IOException + +class ModMailConversationPagingSource(val retrofit: Retrofit, val accessToken: String, val sharedPreferences: SharedPreferences): PagingSource() { + override fun getRefreshKey(state: PagingState): String? { + return null; + } + + override suspend fun load(params: LoadParams): LoadResult { + try { + val response = retrofit.create(RedditAPIKt::class.java) + .getModMailConversations(APIUtils.getOAuthHeader(accessToken), params.key) + + if (response.isSuccessful) { + response.body()?.let { + val json = JSONObject(it) + val conversationIdsArray = json.getJSONArray(JSONUtils.CONVERSATION_IDS_KEY) + if (conversationIdsArray.length() == 0) { + return LoadResult.Page(listOf(), null, null) + } + + val gson = Gson() + val conversations: MutableList = mutableListOf() + + val messagesJSONObject = json.getJSONObject(JSONUtils.MESSAGES_KEY) + for (i in 0 until conversationIdsArray.length()) { + val conversationId = conversationIdsArray.getString(i) + try { + conversations.add(gson.fromJson(json.getJSONObject(JSONUtils.CONVERSATIONS_KEY).getString(conversationId), Conversation::class.java).apply { + for (objId in objIds) { + objId.key?.let { key -> + if (key == "messages") { + objId.id?.let { id -> + try { + messages.add(gson.fromJson(messagesJSONObject.getString(id), ModMessage::class.java)) + } catch (ignore: IOException) { + ignore.printStackTrace() + } + } + } + } + } + }) + } catch (ignore: IOException) { + ignore.printStackTrace() + } + } + + return LoadResult.Page( + conversations, null, conversationIdsArray.getString(conversationIdsArray.length() - 1) + ) + } + } + } catch (e: IOException) { + e.printStackTrace() + } + + return LoadResult.Error(Exception("Error getting response")) + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationViewModel.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationViewModel.kt new file mode 100644 index 00000000..ab7cdec2 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMailConversationViewModel.kt @@ -0,0 +1,40 @@ +package ml.docilealligator.infinityforreddit.mod + +import android.content.SharedPreferences +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.cachedIn +import retrofit2.Retrofit + +class ModMailConversationViewModel( + oauthRetrofit: Retrofit, + accessToken: String, + sharedPreferences: SharedPreferences +) : ViewModel() { + private val pagingSource: ModMailConversationPagingSource = + ModMailConversationPagingSource(oauthRetrofit, accessToken, sharedPreferences) + + val flow = Pager( + PagingConfig(20) + ) { + pagingSource + }.flow.cachedIn(viewModelScope) + + fun refresh() { + pagingSource.invalidate() + } + + @Suppress("UNCHECKED_CAST") + class Factory( + private val oauthRetrofit: Retrofit, + private val accessToken: String, + private val sharedPreferences: SharedPreferences + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return ModMailConversationViewModel(oauthRetrofit, accessToken, sharedPreferences) as T + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMessage.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMessage.kt new file mode 100644 index 00000000..0477a65f --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/mod/ModMessage.kt @@ -0,0 +1,13 @@ +package ml.docilealligator.infinityforreddit.mod + +import com.google.gson.annotations.SerializedName + +data class ModMessage( + @SerializedName("body") var body: String? = null, + @SerializedName("author") var author: Author? = Author(), + @SerializedName("isInternal") var isInternal: Boolean? = null, + @SerializedName("date") var date: String? = null, + @SerializedName("bodyMarkdown") var bodyMarkdown: String? = null, + @SerializedName("id") var id: String? = null, + @SerializedName("participatingAs") var participatingAs: String? = null +) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java index ee18f9ba..55256bbf 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java @@ -197,6 +197,11 @@ public class JSONUtils { public static final String VARIANTS_KEY = "variants"; public static final String PAGE_KEY = "page"; public static final String SEND_REPLIES_KEY = "send_replies"; + public static final String CONVERSATIONS_KEY = "conversations"; + public static final String VIEWER_ID_KEY = "viewerId"; + public static final String CONVERSATION_IDS_KEY = "conversationIds"; + public static final String OBJ_IDS_KEY = "objIds"; + public static final String MESSAGES_KEY = "messages"; @Nullable public static Map parseMediaMetadata(JSONObject data) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dd92a30e..08c853c8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,7 @@ Subscription Comment Filter Customize Comment Filter + Modmail Open navigation drawer Close navigation drawer @@ -1524,5 +1525,7 @@ Download Gif Download Video Download All Gallery Images + + Hello blank fragment diff --git a/build.gradle b/build.gradle index 53e3feeb..c213ce63 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,8 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.5.0' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24' + classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.9.24-1.0.20" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files