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