diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index e1eea1d..2b8a50f 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 6d5302a..9aaa9ca 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,6 +4,7 @@ plugins {
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'kotlin-parcelize'
+ id 'kotlin-kapt'
}
android {
@@ -14,8 +15,8 @@ android {
applicationId "com.doubtless.doubtless"
minSdk 28
targetSdk 33
- versionCode 6
- versionName "0.1.3"
+ versionCode 10
+ versionName "0.1.7-poll"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.debug
@@ -72,6 +73,17 @@ dependencies {
implementation 'com.amplitude:analytics-android:1.+' // works with .+ and not 1.0 :/
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
+ def room_version = "2.5.2"
+
+ implementation("androidx.room:room-runtime:$room_version")
+ annotationProcessor("androidx.room:room-compiler:$room_version")
+
+ // To use Kotlin annotation processing tool (kapt)
+ kapt("androidx.room:room-compiler:$room_version")
+
+ // optional - Kotlin Extensions and Coroutines support for Room
+ implementation("androidx.room:room-ktx:$room_version")
+
// server
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
diff --git a/app/src/main/java/com/doubtless/doubtless/analytics/AnalyticsTracker.kt b/app/src/main/java/com/doubtless/doubtless/analytics/AnalyticsTracker.kt
index 619be60..d9f9066 100644
--- a/app/src/main/java/com/doubtless/doubtless/analytics/AnalyticsTracker.kt
+++ b/app/src/main/java/com/doubtless/doubtless/analytics/AnalyticsTracker.kt
@@ -10,6 +10,16 @@ class AnalyticsTracker constructor(
private val userManager: UserManager
) {
+ fun trackTagsFragment(tag: String) {
+ val map = hashMapOf()
+ map.putAll(getCommonAttrs())
+
+ map["tag"] = tag
+
+ amplitude.track("tag_feed_viewed", map)
+ }
+
+
fun trackLoginStarted() {
val map = hashMapOf()
map.putAll(getCommonAttrs())
@@ -58,7 +68,7 @@ class AnalyticsTracker constructor(
fun trackDoubtUpVoted(doubtData: DoubtData) {
val map = getCommonAttrs().toMutableMap().apply {
- this["doubt_data"] = doubtData.toString()
+ this["doubt_data"] = doubtData.toString()
}
amplitude.track("doubt_upvote", map)
@@ -86,7 +96,9 @@ class AnalyticsTracker constructor(
map["app_version_code"] = BuildConfig.VERSION_CODE.toString()
val user = userManager.getCachedUserData() ?: return map
+
map["user_id"] = user.id.toString()
+ map["user_email"] = user.email.toString()
if (user.local_user_attr?.tags != null)
map["user_tags"] = user.local_user_attr.tags.toString()
diff --git a/app/src/main/java/com/doubtless/doubtless/constants/FirestoreCollection.kt b/app/src/main/java/com/doubtless/doubtless/constants/FirestoreCollection.kt
index c4ca8ee..97fe5d2 100644
--- a/app/src/main/java/com/doubtless/doubtless/constants/FirestoreCollection.kt
+++ b/app/src/main/java/com/doubtless/doubtless/constants/FirestoreCollection.kt
@@ -1,7 +1,5 @@
package com.doubtless.doubtless.constants
-import com.doubtless.doubtless.BuildConfig
-
class FirestoreCollection {
companion object {
@@ -20,6 +18,11 @@ class FirestoreCollection {
val UPVOTE_DATA_USERS = "upvoted_users"
val DOWNVOTE_DATA_USERS = "downvoted_users"
+ val NOTIFICATION = "notifications"
+
+ const val TAG_MY_COLLEGE = "My College"
+ const val TAG_ALL = "All"
+
}
diff --git a/app/src/main/java/com/doubtless/doubtless/constants/GamificationConstants.kt b/app/src/main/java/com/doubtless/doubtless/constants/GamificationConstants.kt
new file mode 100644
index 0000000..156840d
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/constants/GamificationConstants.kt
@@ -0,0 +1,5 @@
+package com.doubtless.doubtless.constants
+
+object GamificationConstants {
+ val MENTOR_XP_THRESHOLD = 1000
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/di/AppCompositionRoot.kt b/app/src/main/java/com/doubtless/doubtless/di/AppCompositionRoot.kt
index f8f7d63..d932728 100644
--- a/app/src/main/java/com/doubtless/doubtless/di/AppCompositionRoot.kt
+++ b/app/src/main/java/com/doubtless/doubtless/di/AppCompositionRoot.kt
@@ -9,6 +9,7 @@ import com.amplitude.android.Configuration
import com.doubtless.doubtless.DoubtlessApp
import com.doubtless.doubtless.R
import com.doubtless.doubtless.analytics.AnalyticsTracker
+import com.doubtless.doubtless.localDatabase.AppDatabase
import com.doubtless.doubtless.navigation.FragNavigator
import com.doubtless.doubtless.navigation.Router
import com.doubtless.doubtless.network.DoubtlessServer
@@ -23,13 +24,15 @@ import com.doubtless.doubtless.screens.dashboard.usecases.DeleteAccountUseCase
import com.doubtless.doubtless.screens.dashboard.usecases.FetchUserDataUseCase
import com.doubtless.doubtless.screens.dashboard.usecases.FetchUserFeedByDateUseCase
import com.doubtless.doubtless.screens.doubt.DoubtData
-import com.doubtless.doubtless.screens.doubt.usecases.DoubtDataSharedPrefUseCase
-import com.doubtless.doubtless.screens.doubt.usecases.PostDoubtUseCase
-import com.doubtless.doubtless.screens.doubt.usecases.VotingUseCase
+import com.doubtless.doubtless.screens.doubt.usecases.*
import com.doubtless.doubtless.screens.home.entities.FeedConfig
import com.doubtless.doubtless.screens.home.usecases.FetchFeedByDateUseCase
import com.doubtless.doubtless.screens.home.usecases.FetchFeedByPopularityUseCase
import com.doubtless.doubtless.screens.home.usecases.FetchHomeFeedUseCase
+import com.doubtless.doubtless.screens.inAppNotification.dao.InAppNotificationDao
+import com.doubtless.doubtless.screens.inAppNotification.usecases.FetchInAppNotificationUseCase
+import com.doubtless.doubtless.screens.inAppNotification.usecases.FetchUnreadNotificationUseCase
+import com.doubtless.doubtless.screens.inAppNotification.usecases.MarkInAppNotificationsReadUseCase
import com.doubtless.doubtless.screens.main.MainActivity
import com.doubtless.doubtless.screens.main.MainFragment
import com.doubtless.doubtless.screens.onboarding.usecases.AddOnBoardingDataUseCase
@@ -84,11 +87,15 @@ class AppCompositionRoot(appContext: DoubtlessApp) {
}
private fun getFetchFeedByPopularityUseCase(): FetchFeedByPopularityUseCase {
- return FetchFeedByPopularityUseCase(FirebaseFirestore.getInstance())
+ return FetchFeedByPopularityUseCase(getMentorsWhoInteractedUseCase(), FirebaseFirestore.getInstance())
}
private fun getFetchFeedByDataUseCase(): FetchFeedByDateUseCase {
- return FetchFeedByDateUseCase(FirebaseFirestore.getInstance())
+ return FetchFeedByDateUseCase(getMentorsWhoInteractedUseCase(), FirebaseFirestore.getInstance())
+ }
+
+ private fun getMentorsWhoInteractedUseCase(): FetchInteractedMentorDataForDoubt {
+ return FetchInteractedMentorDataForDoubt(FirebaseFirestore.getInstance())
}
// --------- Answer Screen ------------
@@ -132,14 +139,54 @@ class AppCompositionRoot(appContext: DoubtlessApp) {
return PostDoubtUseCase(getServer())
}
- // ------- Common --------
+ // --------- InApp Notification ----------
+
+ fun getFetchNotificationUseCase(): FetchInAppNotificationUseCase {
+ return FetchInAppNotificationUseCase(
+ getInAppNotificationDao(),
+ getFetchUnreadNotificationUseCase()
+ )
+ }
+
+ fun getMarkInAppNotificationsReadUseCase(): MarkInAppNotificationsReadUseCase {
+ return MarkInAppNotificationsReadUseCase(
+ getInAppNotificationDao(),
+ FirebaseFirestore.getInstance()
+ )
+ }
+
+ private fun getFetchUnreadNotificationUseCase(): FetchUnreadNotificationUseCase {
+ return FetchUnreadNotificationUseCase(FirebaseFirestore.getInstance(), getUserManager())
+ }
+
+ private fun getInAppNotificationDao(): InAppNotificationDao {
+ return AppDatabase.getDbInstance().inAppNotificationDao()
+ }
+
+ // ------- Doubt Preview --------
fun getAnswerVotingDoubtCase(answerData: AnswerData): VotingUseCase {
- return VotingUseCase(FirebaseFirestore.getInstance(), getUserManager().getCachedUserData()!!, true, answerData, null)
+ return VotingUseCase(
+ FirebaseFirestore.getInstance(),
+ getUserManager().getCachedUserData()!!,
+ true,
+ answerData,
+ null
+ )
}
fun getDoubtVotingDoubtCase(doubtData: DoubtData): VotingUseCase {
- return VotingUseCase(FirebaseFirestore.getInstance(), getUserManager().getCachedUserData()!!, false, null, doubtData)
+ return VotingUseCase(
+ FirebaseFirestore.getInstance(),
+ getUserManager().getCachedUserData()!!,
+ false,
+ null,
+ doubtData
+ )
+ }
+
+ fun getFetchDoubtDataFromDoubtIdUseCase(): FetchDoubtDataFromDoubtIdUseCase {
+ return FetchDoubtDataFromDoubtIdUseCase(FirebaseFirestore.getInstance())
}
// ------- User ---------
@@ -200,21 +247,54 @@ class AppCompositionRoot(appContext: DoubtlessApp) {
return null
}
+ fun getCreateFragmentNavigator(mainActivity: MainActivity): FragNavigator? {
+ val createFrag =
+ (mainActivity.supportFragmentManager.findFragmentByTag("MainFragment") as MainFragment?)
+ ?.childFragmentManager?.findFragmentByTag("mainfrag_1")
+
+ if (createFrag != null) {
+ return DoubtlessApp.getInstance().getAppCompRoot()
+ .getFragNavigator(createFrag.childFragmentManager, R.id.bottomNav_child_container)
+ }
+
+ return null
+ }
+
+ fun getInAppFragNavigator(mainActivity: MainActivity): FragNavigator? {
+
+ val inAppFrag =
+ (mainActivity.supportFragmentManager.findFragmentByTag("MainFragment") as MainFragment?)
+ ?.childFragmentManager?.findFragmentByTag("mainfrag_2")
+
+ if (inAppFrag != null) {
+ return DoubtlessApp.getInstance().getAppCompRoot()
+ .getFragNavigator(inAppFrag.childFragmentManager, R.id.bottomNav_child_container)
+ }
+
+ return null
+ }
+
fun getDashboardFragNavigator(mainActivity: MainActivity): FragNavigator? {
val dashboardFrag =
(mainActivity.supportFragmentManager.findFragmentByTag("MainFragment") as MainFragment?)
- ?.childFragmentManager?.findFragmentByTag("mainfrag_2")
+ ?.childFragmentManager?.findFragmentByTag("mainfrag_3")
if (dashboardFrag != null) {
return DoubtlessApp.getInstance().getAppCompRoot()
- .getFragNavigator(dashboardFrag.childFragmentManager, R.id.bottomNav_child_container)
+ .getFragNavigator(
+ dashboardFrag.childFragmentManager,
+ R.id.bottomNav_child_container
+ )
}
return null
}
- private fun getFragNavigator(supportFragmentManager: FragmentManager, @IdRes containerId: Int ): FragNavigator {
+ private fun getFragNavigator(
+ supportFragmentManager: FragmentManager,
+ @IdRes containerId: Int
+ ): FragNavigator {
return FragNavigator(
containerId,
supportFragmentManager
@@ -272,6 +352,7 @@ class AppCompositionRoot(appContext: DoubtlessApp) {
return remoteConfig
}
+ // --------- Dashboard -----------
fun getFetchUserDataUseCase(): FetchUserDataUseCase {
return FetchUserDataUseCase(
FetchUserFeedByDateUseCase(FirebaseFirestore.getInstance()),
@@ -279,9 +360,17 @@ class AppCompositionRoot(appContext: DoubtlessApp) {
)
}
- fun getDeleteAccountUseCase() : DeleteAccountUseCase {
+ fun getDeleteAccountUseCase(): DeleteAccountUseCase {
return DeleteAccountUseCase(
FirebaseFirestore.getInstance()
)
}
+
+ // --------- Home Main Screen -----------
+
+ fun getFetchFilterTagsUseCase(): FetchFilterTagsUseCase {
+ return FetchFilterTagsUseCase(
+ FirebaseFirestore.getInstance()
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/localDatabase/AppDatabase.kt b/app/src/main/java/com/doubtless/doubtless/localDatabase/AppDatabase.kt
new file mode 100644
index 0000000..950304a
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/localDatabase/AppDatabase.kt
@@ -0,0 +1,33 @@
+package com.doubtless.doubtless.localDatabase
+
+import androidx.room.*
+import com.doubtless.doubtless.DoubtlessApp
+import com.doubtless.doubtless.screens.inAppNotification.dao.InAppNotificationDao
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+
+class AppDatabase {
+ @Database(entities = [InAppNotificationEntity::class], version = 1)
+ @TypeConverters(Converters::class)
+ abstract class AppDB : RoomDatabase() {
+ abstract fun inAppNotificationDao(): InAppNotificationDao
+ }
+
+ companion object {
+
+ private var database: AppDB? = null
+
+ @Synchronized
+ fun getDbInstance(): AppDB {
+
+ if (database == null) {
+ database = Room.databaseBuilder(
+ context = DoubtlessApp.getInstance(),
+ klass = AppDB::class.java,
+ name = "doubtless-db"
+ ).build()
+ }
+
+ return database!!
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/localDatabase/Converter.kt b/app/src/main/java/com/doubtless/doubtless/localDatabase/Converter.kt
new file mode 100644
index 0000000..fbdd0f5
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/localDatabase/Converter.kt
@@ -0,0 +1,22 @@
+package com.doubtless.doubtless.localDatabase
+
+import androidx.room.TypeConverter
+import com.google.firebase.Timestamp
+import com.google.gson.Gson
+import java.util.*
+
+class Converters {
+
+ private val gson = Gson()
+
+ @TypeConverter
+ fun longToTimestamp(value: Long): Timestamp {
+ return Timestamp(Date(value))
+ }
+
+ @TypeConverter
+ fun timestampToLong(timestamp: Timestamp): Long {
+ return timestamp.toDate().time
+ }
+
+}
diff --git a/app/src/main/java/com/doubtless/doubtless/navigation/FragNavigator.kt b/app/src/main/java/com/doubtless/doubtless/navigation/FragNavigator.kt
index 6655236..d1a5021 100644
--- a/app/src/main/java/com/doubtless/doubtless/navigation/FragNavigator.kt
+++ b/app/src/main/java/com/doubtless/doubtless/navigation/FragNavigator.kt
@@ -5,6 +5,7 @@ import androidx.fragment.app.FragmentManager
import com.doubtless.doubtless.R
import com.doubtless.doubtless.screens.answers.AnswersFragment
import com.doubtless.doubtless.screens.doubt.DoubtData
+import com.doubtless.doubtless.screens.poll.CreatePollFragment
import com.doubtless.doubtless.screens.search.SearchFragment
class FragNavigator constructor(
@@ -44,4 +45,17 @@ class FragNavigator constructor(
.commitAllowingStateLoss()
}
+ fun moveToCreatePollFragment(){
+ supportFragmentManager.beginTransaction()
+ .setCustomAnimations(
+ /* enter = */ R.anim.slide_in_right,
+ /* exit = */ R.anim.slide_out_left,
+ /* popEnter = */ R.anim.slide_in_left,
+ /* popExit = */ R.anim.slide_out_right)
+ .replace(containerId, CreatePollFragment())
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commitAllowingStateLoss()
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/navigation/Router.kt b/app/src/main/java/com/doubtless/doubtless/navigation/Router.kt
index b650100..9244e0f 100644
--- a/app/src/main/java/com/doubtless/doubtless/navigation/Router.kt
+++ b/app/src/main/java/com/doubtless/doubtless/navigation/Router.kt
@@ -5,6 +5,7 @@ import android.content.Intent
import com.doubtless.doubtless.screens.onboarding.OnBoardingActivity
import com.doubtless.doubtless.screens.auth.LoginActivity
import com.doubtless.doubtless.screens.main.MainActivity
+import com.doubtless.doubtless.screens.poll.CreatePollFragment
class Router {
@@ -22,5 +23,4 @@ class Router {
val i = Intent(activity, OnBoardingActivity::class.java)
activity.startActivity(i)
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerData.kt b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerData.kt
index 5042fdc..c1c2576 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerData.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerData.kt
@@ -1,11 +1,7 @@
package com.doubtless.doubtless.screens.answers
-import android.os.Handler
-import android.os.Looper
-import android.widget.Toast
import com.doubtless.doubtless.DoubtlessApp
import com.google.errorprone.annotations.Keep
-import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.PropertyName
import com.google.firebase.firestore.QuerySnapshot
import com.google.firebase.firestore.ServerTimestamp
@@ -56,6 +52,10 @@ data class AnswerData(
@get:PropertyName("created_on")
@set:PropertyName("created_on")
var date: Date? = null,
+ @SerializedName("xp_count")
+ @get:PropertyName("xp_count")
+ @set:PropertyName("xp_count")
+ var xpCount: Long? = 0,
) {
companion object {
@@ -77,7 +77,8 @@ data class AnswerData(
authorYear = answerSnapshot.getField("author_year"),
description = answerSnapshot.getField("description"),
netVotes = (answerSnapshot.getField("net_votes") as Float?) ?: 0f,
- date = answerSnapshot.getField("created_on")
+ date = answerSnapshot.getField("created_on"),
+ xpCount = (answerSnapshot.getField("xp_count") as Long?) ?: 0
)
)
} catch (e: Exception) {
@@ -92,7 +93,13 @@ data class AnswerData(
}
fun toAnswerDoubtEntity(answerData: AnswerData): AnswerDoubtEntity {
- return AnswerDoubtEntity(AnswerDoubtEntity.TYPE_ANSWER, null, answerData)
+ return AnswerDoubtEntity(
+ type = AnswerDoubtEntity.TYPE_ANSWER,
+ doubt = null,
+ answer = answerData,
+ answerVotingUseCase = DoubtlessApp.getInstance().getAppCompRoot()
+ .getAnswerVotingDoubtCase(answerData)
+ )
}
}
}
@@ -112,7 +119,9 @@ data class PublishAnswerRequest(
@SerializedName("author_year")
var authorYear: String? = null,
@SerializedName("description")
- var description: String? = null
+ var description: String? = null,
+ @SerializedName("xp_count")
+ var xpCount: Long = 0
) {
companion object {
fun toAnswerData(
@@ -128,7 +137,8 @@ data class PublishAnswerRequest(
authorYear = publishAnswerRequest.authorYear,
description = publishAnswerRequest.description,
netVotes = 0f,
- date = Date()
+ date = Date(),
+ xpCount = publishAnswerRequest.xpCount
)
}
}
@@ -169,7 +179,11 @@ data class PublishAnswerResponse(
@SerializedName("net_votes")
@get:PropertyName("net_votes")
@set:PropertyName("net_votes")
- var netVotes: Float = 0f
+ var netVotes: Float = 0f,
+ @SerializedName("xp_count")
+ @get:PropertyName("xp_count")
+ @set:PropertyName("xp_count")
+ var xpCount: Long = 0
) {
fun toAnswerData(): AnswerData {
return AnswerData(
@@ -182,7 +196,8 @@ data class PublishAnswerResponse(
authorYear = this.authorYear,
description = this.description,
netVotes = this.netVotes,
- date = null
+ date = null,
+ xpCount = this.xpCount
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtEntity.kt b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtEntity.kt
index 15a4c3b..5457bba 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtEntity.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtEntity.kt
@@ -1,11 +1,14 @@
package com.doubtless.doubtless.screens.answers
import com.doubtless.doubtless.screens.doubt.DoubtData
+import com.doubtless.doubtless.screens.doubt.usecases.VotingUseCase
data class AnswerDoubtEntity(
val type: Int,
val doubt: DoubtData? = null,
- val answer: AnswerData? = null
+ val answer: AnswerData? = null,
+ val answerVotingUseCase: VotingUseCase? = null,
+ val doubtVotingUseCase: VotingUseCase? = null
) {
companion object {
const val TYPE_DOUBT = 1
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtsAdapter.kt b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtsAdapter.kt
index 89e364b..f16bfaa 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtsAdapter.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswerDoubtsAdapter.kt
@@ -75,7 +75,7 @@ class AnswerDoubtsAdapter(
holder.setData(doubtAnswerEntities[position].doubt!!)
if (holder is AnswerViewHolder)
- holder.setData(doubtAnswerEntities[position].answer!!)
+ holder.setData(doubtAnswerEntities[position].answer!!, doubtAnswerEntities[position].answerVotingUseCase!!)
if (holder is EnterAnswerViewHolder)
holder.setData(user)
@@ -98,6 +98,7 @@ class AnswerDoubtsAdapter(
index = 2,
element = answer
) // first 2 are doubt and enter answer view
+
notifyItemInserted(1)
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersFragment.kt
index 3636b44..f17fc90 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersFragment.kt
@@ -1,16 +1,15 @@
package com.doubtless.doubtless.screens.answers
import android.os.Bundle
-import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
+import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import com.doubtless.doubtless.DoubtlessApp
-import com.doubtless.doubtless.R
import com.doubtless.doubtless.analytics.AnalyticsTracker
import com.doubtless.doubtless.databinding.FragmentAnswersBinding
import com.doubtless.doubtless.navigation.FragNavigator
@@ -18,8 +17,8 @@ import com.doubtless.doubtless.screens.answers.usecases.PublishAnswerUseCase
import com.doubtless.doubtless.screens.answers.viewholder.EnterAnswerViewHolder
import com.doubtless.doubtless.screens.auth.usecases.UserManager
import com.doubtless.doubtless.screens.doubt.DoubtData
-import com.doubtless.doubtless.screens.doubt.view.ViewDoubtsViewModel
import com.doubtless.doubtless.screens.main.MainActivity
+import com.doubtless.doubtless.screens.main.MainFragment
class AnswersFragment : Fragment() {
@@ -46,8 +45,23 @@ class AnswersFragment : Fragment() {
userManager = DoubtlessApp.getInstance().getAppCompRoot().getUserManager()
analyticsTracker = DoubtlessApp.getInstance().getAppCompRoot().getAnalyticsTracker()
- val _navigator = DoubtlessApp.getInstance().getAppCompRoot()
- .getHomeFragNavigator(requireActivity() as MainActivity)
+
+ var _navigator: FragNavigator? = null
+
+ // uuhh!!
+ val currentSelectedFrag =
+ (requireActivity() as MainActivity).getMainFragment()?.getCurrentSelectedElement()
+
+ // encapsulate this logic
+ if (currentSelectedFrag is MainFragment.CurrentSelectedBottomNavFrag.HomeFrag) {
+ _navigator = DoubtlessApp.getInstance().getAppCompRoot()
+ .getHomeFragNavigator(requireActivity() as MainActivity)
+ }
+
+ if (currentSelectedFrag is MainFragment.CurrentSelectedBottomNavFrag.DashboardFrag) {
+ _navigator = DoubtlessApp.getInstance().getAppCompRoot()
+ .getDashboardFragNavigator(requireActivity() as MainActivity)
+ }
if (_navigator != null)
navigator = _navigator
@@ -104,7 +118,8 @@ class AnswersFragment : Fragment() {
authorPhotoUrl = userManager.getCachedUserData()!!.photoUrl,
authorCollege = userManager.getCachedUserData()!!.local_user_attr!!.college,
authorYear = userManager.getCachedUserData()!!.local_user_attr!!.year,
- description = publishAnswerDTO.description
+ description = publishAnswerDTO.description,
+ xpCount = userManager.getCachedUserData()!!.xpCount ?: 0
)
)
}
@@ -122,27 +137,48 @@ class AnswersFragment : Fragment() {
}
viewModel.answerDoubtEntities.observe(viewLifecycleOwner) {
- if (it == null) return@observe
- adapter.appendAnswer(it)
- viewModel.notifyAnswersConsumed()
+ when (it) {
+ is AnswersViewModel.Result.Loading -> {
+ binding.progressPostAnswer.visibility = View.VISIBLE
+ }
+
+ is AnswersViewModel.Result.Success -> {
+ adapter.appendAnswer(it.data)
+ viewModel.notifyAnswersConsumed()
+ binding.progressPostAnswer.visibility = View.GONE
+ }
+
+ is AnswersViewModel.Result.Error -> {
+ Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
+ binding.progressPostAnswer.visibility = View.GONE
+ }
+ }
}
viewModel.publishAnswerStatus.observe(viewLifecycleOwner) {
- if (it is PublishAnswerUseCase.Result.Success) {
- binding.progressPostAnswer.visibility = View.GONE
- Toast.makeText(requireContext(), "Successfully posted!", Toast.LENGTH_SHORT).show()
- adapter.appendAnswerAtFirst(answerData = it.answerData)
- // this will increase the count across screens as the same reference was passed to the arguments.
- // Its generally not a good thing to do.t(it.answerData)
- doubtData.no_answers += 1
- }
+ when (it) {
+ is PublishAnswerUseCase.Result.Success -> {
- if (it is PublishAnswerUseCase.Result.Error){
- binding.progressPostAnswer.visibility = View.GONE
- Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
- }
- if (it is PublishAnswerUseCase.Result.Loading){
- binding.progressPostAnswer.visibility = View.VISIBLE
+ binding.progressPostAnswer.visibility = View.GONE
+
+ Toast.makeText(requireContext(), "Successfully posted!", Toast.LENGTH_SHORT)
+ .show()
+
+ adapter.appendAnswerAtFirst(answerData = it.answerData)
+
+ // this will increase the count across screens as the same reference was passed to the arguments.
+ // Its generally not a good thing to do.
+ doubtData.no_answers += 1
+ }
+
+ is PublishAnswerUseCase.Result.Error -> {
+ binding.progressPostAnswer.visibility = View.GONE
+ Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
+ }
+
+ is PublishAnswerUseCase.Result.Loading -> {
+ binding.progressPostAnswer.visibility = View.VISIBLE
+ }
}
}
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersViewModel.kt b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersViewModel.kt
index 041f96c..1bb1464 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersViewModel.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/answers/AnswersViewModel.kt
@@ -1,6 +1,7 @@
package com.doubtless.doubtless.screens.answers
import androidx.lifecycle.*
+import com.doubtless.doubtless.DoubtlessApp
import com.doubtless.doubtless.screens.answers.usecases.FetchAnswerUseCase
import com.doubtless.doubtless.screens.answers.usecases.PublishAnswerUseCase
import com.doubtless.doubtless.screens.auth.usecases.UserManager
@@ -15,28 +16,55 @@ class AnswersViewModel(
private val doubtData: DoubtData
) : ViewModel() {
- private val _answerDoubtEntities = MutableLiveData(
- listOf(
- AnswerDoubtEntity(AnswerDoubtEntity.TYPE_DOUBT, doubtData, null),
- AnswerDoubtEntity(AnswerDoubtEntity.TYPE_ANSWER_ENTER, null, null)
+ sealed class Result {
+ class Success(val data: List) : Result()
+ object Loading : Result()
+ class Error(val message: String) : Result()
+ }
+
+ private val _answerDoubtEntities = MutableLiveData(
+ Result.Success(
+ listOf(
+ AnswerDoubtEntity(
+ type = AnswerDoubtEntity.TYPE_DOUBT,
+ doubt = doubtData,
+ answer = null,
+ answerVotingUseCase = null,
+ doubtVotingUseCase = DoubtlessApp.getInstance().getAppCompRoot()
+ .getDoubtVotingDoubtCase(doubtData)
+ ),
+ AnswerDoubtEntity(AnswerDoubtEntity.TYPE_ANSWER_ENTER, null, null)
+ )
)
)
- val answerDoubtEntities: LiveData?> = _answerDoubtEntities
+ val answerDoubtEntities: LiveData = _answerDoubtEntities
private val _publishAnswerStatus = MutableLiveData()
val publishAnswerStatus: LiveData = _publishAnswerStatus
fun fetchAnswers() = viewModelScope.launch(Dispatchers.IO) {
+
+ _answerDoubtEntities.postValue(Result.Loading)
+
val result = fetchAnswerUseCase.fetchAnswers()
- if (result is FetchAnswerUseCase.Result.Success) {
- _answerDoubtEntities.postValue(result.data.map {
- AnswerData.toAnswerDoubtEntity(it)
- })
- } else {
- /* no-op */
+ when (result) {
+ is FetchAnswerUseCase.Result.Success -> {
+ _answerDoubtEntities.postValue(
+ Result.Success(result.data.map {
+ AnswerData.toAnswerDoubtEntity(it)
+ })
+ )
+ }
+ else -> {
+ _answerDoubtEntities.postValue(
+ Result.Error(
+ (result as FetchAnswerUseCase.Result.Error).message
+ )
+ )
+ }
}
}
@@ -61,7 +89,12 @@ class AnswersViewModel(
) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
- return AnswersViewModel(fetchAnswerUseCase, publishAnswerUseCase, userManager, doubtData) as T
+ return AnswersViewModel(
+ fetchAnswerUseCase,
+ publishAnswerUseCase,
+ userManager,
+ doubtData
+ ) as T
}
}
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/answers/viewholder/AnswerViewHolder.kt b/app/src/main/java/com/doubtless/doubtless/screens/answers/viewholder/AnswerViewHolder.kt
index d6c4ff5..64e1e89 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/answers/viewholder/AnswerViewHolder.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/answers/viewholder/AnswerViewHolder.kt
@@ -1,21 +1,22 @@
package com.doubtless.doubtless.screens.answers.viewholder
import android.view.View
+import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
-import com.doubtless.doubtless.DoubtlessApp
import com.doubtless.doubtless.R
+import com.doubtless.doubtless.constants.GamificationConstants
import com.doubtless.doubtless.screens.answers.AnswerData
import com.doubtless.doubtless.screens.doubt.usecases.VotingUseCase
import com.doubtless.doubtless.utils.Utils
+import com.doubtless.doubtless.utils.addStateListAnimation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Date
-import kotlin.math.ceil
import kotlin.math.floor
@@ -32,8 +33,10 @@ class AnswerViewHolder(itemView: View, private val interactionListener: Interact
private val ivDp: ImageView
private val tvYear: TextView
private val tvVotes: TextView
- private val ivUpVote: ImageView
- private val ivDownVote: ImageView
+ private val upVote: CheckBox
+ private val downVote: CheckBox
+ private val tvCollege: TextView
+ private val userBadge: ImageView
init {
authorName = itemView.findViewById(R.id.tv_author_name)
@@ -42,11 +45,13 @@ class AnswerViewHolder(itemView: View, private val interactionListener: Interact
ivDp = itemView.findViewById(R.id.iv_dp_author)
tvYear = itemView.findViewById(R.id.user_year)
tvVotes = itemView.findViewById(R.id.tv_votes)
- ivUpVote = itemView.findViewById(R.id.iv_votes)
- ivDownVote = itemView.findViewById(R.id.iv_downvote)
+ upVote = itemView.findViewById(R.id.cb_upvote)
+ downVote = itemView.findViewById(R.id.cb_downvote)
+ tvCollege = itemView.findViewById(R.id.tv_college)
+ userBadge = itemView.findViewById(R.id.user_badge)
}
- fun setData(answerData: AnswerData) {
+ fun setData(answerData: AnswerData, answerVotingUseCase: VotingUseCase) {
itemView.setOnClickListener {
interactionListener.onAnswerClicked(answerData, adapterPosition)
@@ -60,16 +65,24 @@ class AnswerViewHolder(itemView: View, private val interactionListener: Interact
time.isVisible = false
}
+ userBadge.isVisible = answerData.xpCount!! > GamificationConstants.MENTOR_XP_THRESHOLD
description.text = answerData.description
- tvYear.text = "| ${answerData.authorYear} Year |"
- val votingUseCase = DoubtlessApp.getInstance().getAppCompRoot().getAnswerVotingDoubtCase(answerData.copy())
- setVotesUi(answerData, votingUseCase)
+ if (answerData.authorYear.equals("passout", ignoreCase = true)) {
+ tvYear.text = "| ${answerData.authorYear} |"
+ } else {
+ tvYear.text = "| ${answerData.authorYear} Year |"
+ }
+
+ tvCollege.text = answerData.authorCollege
+ setVotesUi(answerData, answerVotingUseCase)
- ivUpVote.setOnClickListener {
+ upVote.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
+ if (it.stateListAnimator == null)
+ it.addStateListAnimation(R.animator.scale_votes_icon)
- val result = votingUseCase.upvoteDoubt()
+ val result = answerVotingUseCase.upvoteDoubt()
if (result is VotingUseCase.Result.UpVoted) {
answerData.netVotes += 1
@@ -77,14 +90,16 @@ class AnswerViewHolder(itemView: View, private val interactionListener: Interact
answerData.netVotes -= 1
}
- setVotesUi(answerData, votingUseCase)
+ setVotesUi(answerData, answerVotingUseCase)
}
}
- ivDownVote.setOnClickListener {
+ downVote.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
+ if (it.stateListAnimator == null)
+ it.addStateListAnimation(R.animator.scale_votes_icon)
- val result = votingUseCase.downVoteDoubt()
+ val result = answerVotingUseCase.downVoteDoubt()
if (result is VotingUseCase.Result.DownVoted) {
answerData.netVotes -= 1
@@ -92,7 +107,7 @@ class AnswerViewHolder(itemView: View, private val interactionListener: Interact
answerData.netVotes += 1
}
- setVotesUi(answerData, votingUseCase)
+ setVotesUi(answerData, answerVotingUseCase)
}
}
@@ -102,19 +117,27 @@ class AnswerViewHolder(itemView: View, private val interactionListener: Interact
}
private fun setVotesUi(answerData: AnswerData, votingUseCase: VotingUseCase) {
- ivDownVote.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_off_alt_24))
- ivUpVote.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_off_alt_24))
tvVotes.text = floor(answerData.netVotes).toInt().toString()
CoroutineScope(Dispatchers.Main).launch {
- val currentState = votingUseCase.getUserCurrentState()
- if (currentState == VotingUseCase.UPVOTED)
- ivUpVote.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_filled))
+ when (votingUseCase.getUserCurrentState()) {
- if (currentState == VotingUseCase.DOWNVOTED)
- ivDownVote.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_filled))
+ VotingUseCase.UPVOTED -> {
+ downVote.isClickable = false
+ upVote.isChecked = true
+ }
+
+ VotingUseCase.DOWNVOTED -> {
+ upVote.isClickable = false
+ downVote.isChecked = true
+ }
+
+ else -> {
+ downVote.isClickable = true
+ upVote.isClickable = true
+ }
+ }
}
}
-
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/auth/LoginActivity.kt b/app/src/main/java/com/doubtless/doubtless/screens/auth/LoginActivity.kt
index d15e4bd..ea4de4b 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/auth/LoginActivity.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/auth/LoginActivity.kt
@@ -6,6 +6,7 @@ import android.view.View
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
import com.bumptech.glide.Glide
import com.doubtless.doubtless.DoubtlessApp
import com.doubtless.doubtless.R
@@ -68,6 +69,8 @@ class LoginActivity : AppCompatActivity() {
private fun setupUi() {
window.statusBarColor = getColor(R.color.purple)
+ findViewById(R.id.view_sep).isVisible = false
+
Glide.with(this)
.load("https://lh3.googleusercontent.com/a/AGNmyxaceCDqTACCSoa1e3VimHXoAEQ4IBSYbPk8YTU-J5U=s96-c")
.circleCrop()
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/auth/User.kt b/app/src/main/java/com/doubtless/doubtless/screens/auth/User.kt
index 6e04dde..b32f4f7 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/auth/User.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/auth/User.kt
@@ -29,6 +29,10 @@ data class User(
@get:PropertyName("photoUrl")
@set:PropertyName("photoUrl")
var photoUrl: String? = null,
+ @SerializedName("xpCount")
+ @get:PropertyName("xpCount")
+ @set:PropertyName("xpCount")
+ var xpCount: Long = 0,
@get:Exclude val document_id: String? = null,
@get:Exclude val local_user_attr: UserAttributes? = null
)
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/auth/exception/UserNotFoundException.kt b/app/src/main/java/com/doubtless/doubtless/screens/auth/exception/UserNotFoundException.kt
new file mode 100644
index 0000000..961485b
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/auth/exception/UserNotFoundException.kt
@@ -0,0 +1,3 @@
+package com.doubtless.doubtless.screens.auth.exception
+
+class UserNotFoundException : Exception()
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/auth/login/LoginUtils.kt b/app/src/main/java/com/doubtless/doubtless/screens/auth/login/LoginUtils.kt
new file mode 100644
index 0000000..1468d0c
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/auth/login/LoginUtils.kt
@@ -0,0 +1,8 @@
+package com.doubtless.doubtless.screens.auth.login
+
+import android.app.Activity
+import com.doubtless.doubtless.analytics.AnalyticsTracker
+
+interface LoginUtils {
+ fun logOutUser(analyticsTracker: AnalyticsTracker, activity: Activity)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/auth/login/LoginUtilsImpl.kt b/app/src/main/java/com/doubtless/doubtless/screens/auth/login/LoginUtilsImpl.kt
new file mode 100644
index 0000000..b580342
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/auth/login/LoginUtilsImpl.kt
@@ -0,0 +1,39 @@
+package com.doubtless.doubtless.screens.auth.login
+
+import android.app.Activity
+import android.widget.Toast
+import com.doubtless.doubtless.DoubtlessApp
+import com.doubtless.doubtless.analytics.AnalyticsTracker
+import com.doubtless.doubtless.screens.auth.usecases.UserManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+
+object LoginUtilsImpl : LoginUtils {
+
+ override fun logOutUser(analyticsTracker: AnalyticsTracker, activity: Activity) {
+ analyticsTracker.trackLogout()
+ CoroutineScope(Dispatchers.Main).launch {
+
+ val result = withContext(Dispatchers.IO) {
+ DoubtlessApp.getInstance().getAppCompRoot().getUserManager().onUserLogoutSync()
+ }
+
+ if (result is UserManager.Result.LoggedOut) {
+
+ DoubtlessApp.getInstance().getAppCompRoot().router.moveToLoginActivity(
+ activity
+ )
+ activity.finish()
+
+ } else if (result is UserManager.Result.Error) {
+
+ Toast.makeText(
+ activity, result.message, Toast.LENGTH_LONG
+ ).show() // encapsulate error ui handling
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/auth/usecases/UserManager.kt b/app/src/main/java/com/doubtless/doubtless/screens/auth/usecases/UserManager.kt
index be038cf..7f3d6ee 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/auth/usecases/UserManager.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/auth/usecases/UserManager.kt
@@ -8,7 +8,7 @@ import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
-import java.util.*
+import java.util.UUID
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -19,7 +19,12 @@ class UserManager constructor(
) {
sealed class Result(user: User? = null, isNewUser: Boolean?, error: String? = null) {
- class Success(val user: User, val isNewUser: Boolean, val isOldUserWithNoOnboarding: Boolean) : Result(user, isNewUser)
+ class Success(
+ val user: User,
+ val isNewUser: Boolean,
+ val isOldUserWithNoOnboarding: Boolean
+ ) : Result(user, isNewUser)
+
class Error(val message: String) : Result(null, null, message)
class LoggedOut : Result(null, null, null)
}
@@ -69,7 +74,8 @@ class UserManager constructor(
if (result is UserDataServerUseCase.Result.NewUser) {
userDataStorageUseCase.setUserData(result.newUser)
cachedUserData = result.newUser
- return Result.Success(result.newUser,
+ return Result.Success(
+ result.newUser,
isNewUser = true,
isOldUserWithNoOnboarding = false
)
@@ -78,7 +84,8 @@ class UserManager constructor(
if (result is UserDataServerUseCase.Result.OldUseWithNoOnboardingData) {
userDataStorageUseCase.setUserData(result.oldUser)
cachedUserData = result.oldUser
- return Result.Success(result.oldUser,
+ return Result.Success(
+ result.oldUser,
isNewUser = false,
isOldUserWithNoOnboarding = true
)
@@ -87,7 +94,8 @@ class UserManager constructor(
if (result is UserDataServerUseCase.Result.OldUser) {
userDataStorageUseCase.setUserData(result.oldUser)
cachedUserData = result.oldUser
- return Result.Success(result.oldUser,
+ return Result.Success(
+ result.oldUser,
isNewUser = false,
isOldUserWithNoOnboarding = false
)
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/common/ExtraOptionsButtonHolder.kt b/app/src/main/java/com/doubtless/doubtless/screens/common/ExtraOptionsButtonHolder.kt
new file mode 100644
index 0000000..312fca3
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/common/ExtraOptionsButtonHolder.kt
@@ -0,0 +1,27 @@
+package com.doubtless.doubtless.screens.common
+
+import android.view.View
+import android.widget.ImageView
+import androidx.recyclerview.widget.RecyclerView
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.navigation.FragNavigator
+
+class ExtraOptionsButtonHolder(val view: View, val interactionListener: InteractionListener)
+ : RecyclerView.ViewHolder(view) {
+
+
+ interface InteractionListener{
+ fun onCreatePollClicked()
+ }
+
+ private val createPoll: ImageView
+
+
+ init {
+ createPoll = view.findViewById(R.id.ic_poll)
+
+ createPoll.setOnClickListener {
+ interactionListener.onCreatePollClicked()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/common/GenericFeedAdapter.kt b/app/src/main/java/com/doubtless/doubtless/screens/common/GenericFeedAdapter.kt
index 339896b..98b49f8 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/common/GenericFeedAdapter.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/common/GenericFeedAdapter.kt
@@ -8,10 +8,10 @@ import com.doubtless.doubtless.DoubtlessApp
import com.doubtless.doubtless.R
import com.doubtless.doubtless.screens.dashboard.viewholder.UserProfileViewHolder
import com.doubtless.doubtless.screens.doubt.DoubtData
-import com.doubtless.doubtless.screens.doubt.usecases.VotingUseCase
import com.doubtless.doubtless.screens.doubt.view.viewholder.DoubtPreviewViewHolder
import com.doubtless.doubtless.screens.home.entities.FeedEntity
import com.doubtless.doubtless.screens.home.viewholders.HomeSearchViewHolder
+import com.doubtless.doubtless.screens.poll.ViewPollViewHolder
class GenericFeedAdapter(
private val genericFeedEntities: MutableList,
@@ -20,11 +20,12 @@ class GenericFeedAdapter(
) : RecyclerView.Adapter() {
interface InteractionListener {
- fun onSearchBarClicked()
fun onDoubtClicked(doubtData: DoubtData, position: Int)
fun onSignOutClicked()
fun onSubmitFeedbackClicked()
fun onDeleteAccountClicked()
+ fun onCreatePollClicked()
+ fun onPollOptionClicked(position: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@@ -36,7 +37,9 @@ class GenericFeedAdapter(
return UserProfileViewHolder(
view,
object : UserProfileViewHolder.InteractionListener {
- override fun onDeleteAccountClicked() = interactionListener.onDeleteAccountClicked()
+ override fun onDeleteAccountClicked() =
+ interactionListener.onDeleteAccountClicked()
+
override fun onSignOutClicked() = interactionListener.onSignOutClicked()
override fun onSubmitFeedbackClicked() =
interactionListener.onSubmitFeedbackClicked()
@@ -46,9 +49,8 @@ class GenericFeedAdapter(
FeedEntity.TYPE_DOUBT -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.doubt_layout, parent, false)
- return DoubtPreviewViewHolder(
- view = view,
- showVotingLayout = false,
+ return DoubtPreviewViewHolder(view = view,
+ showVotingLayout = true,
interactionListener = object : DoubtPreviewViewHolder.InteractionListener {
override fun onDoubtClicked(doubtData: DoubtData, position: Int) {
interactionListener.onDoubtClicked(doubtData, position)
@@ -56,23 +58,10 @@ class GenericFeedAdapter(
})
}
- FeedEntity.TYPE_SEARCH -> {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.layout_home_search, parent, false)
- return HomeSearchViewHolder(
- view,
- object : HomeSearchViewHolder.InteractionListener {
- override fun onLayoutClicked() {
- interactionListener.onSearchBarClicked()
- }
- })
- }
-
FeedEntity.TYPE_SEARCH_RESULT -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.doubt_layout, parent, false)
- return DoubtPreviewViewHolder(
- view = view,
+ return DoubtPreviewViewHolder(view = view,
showVotingLayout = false,
interactionListener = object : DoubtPreviewViewHolder.InteractionListener {
override fun onDoubtClicked(doubtData: DoubtData, position: Int) {
@@ -80,6 +69,32 @@ class GenericFeedAdapter(
}
})
}
+
+ FeedEntity.TYPE_BUTTONS -> {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.layout_home_buttons, parent, false)
+ return ExtraOptionsButtonHolder(view= view,
+ object : ExtraOptionsButtonHolder.InteractionListener{
+ override fun onCreatePollClicked() {
+ interactionListener.onCreatePollClicked()
+ }
+
+ }
+ )
+ }
+
+ FeedEntity.TYPE_POLL -> {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.view_poll, parent, false)
+ return ViewPollViewHolder(
+ view = view,
+ interactionListener = object : ViewPollViewHolder.InteractionListener {
+ override fun onPollOptionClicked(position: Int) {
+ interactionListener.onPollOptionClicked(position)
+ }
+ }
+ )
+ }
}
throw Exception("type is not defined")
@@ -90,11 +105,17 @@ class GenericFeedAdapter(
userManager = DoubtlessApp.getInstance().getAppCompRoot().getUserManager()
)
- if (holder is DoubtPreviewViewHolder && getItemViewType(position) == FeedEntity.TYPE_DOUBT)
- holder.setData(genericFeedEntities[position].doubt!!)
+ if (holder is DoubtPreviewViewHolder && getItemViewType(position) == FeedEntity.TYPE_DOUBT) holder.setData(
+ genericFeedEntities[position].doubt!!
+ )
+
+ if (holder is DoubtPreviewViewHolder && getItemViewType(position) == FeedEntity.TYPE_SEARCH_RESULT) holder.setData(
+ genericFeedEntities[position].search_doubt!!.toDoubtData()
+ )
+
+ if (holder is ViewPollViewHolder && getItemViewType(position) == FeedEntity.TYPE_POLL)
+ holder.setData(genericFeedEntities[position])
- if (holder is DoubtPreviewViewHolder && getItemViewType(position) == FeedEntity.TYPE_SEARCH_RESULT)
- holder.setData(genericFeedEntities[position].search_doubt!!.toDoubtData())
if (position == itemCount - 1) {
onLastItemReached.invoke()
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardContainerFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardContainerFragment.kt
index d84f9ff..eaf698b 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardContainerFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardContainerFragment.kt
@@ -8,6 +8,7 @@ import com.doubtless.doubtless.R
import com.doubtless.doubtless.navigation.FragNavigator
import com.doubtless.doubtless.navigation.OnBackPressListener
import com.doubtless.doubtless.screens.main.MainActivity
+import com.doubtless.doubtless.screens.main.MainFragment
class DashboardContainerFragment : Fragment(R.layout.fragment_home) {
@@ -28,7 +29,15 @@ class DashboardContainerFragment : Fragment(R.layout.fragment_home) {
private val onBackPressListener = object : OnBackPressListener {
override fun onBackPress(): Boolean {
- return navigator.onBackPress()
+
+ val backPressConsumed = navigator.onBackPress()
+
+ return if (backPressConsumed)
+ true
+ else {
+ (parentFragment as? MainFragment)?.selectHomeBottomNavElement()
+ true
+ }
}
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardFragment.kt
index 496644e..d99e6cb 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/dashboard/DashboardFragment.kt
@@ -50,6 +50,8 @@ class DashboardFragment : Fragment() {
private lateinit var tracker: AnalyticsTracker
+ private var onCreateEventUnConsumed = true
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
tracker = DoubtlessApp.getInstance().getAppCompRoot().getAnalyticsTracker()
@@ -57,10 +59,9 @@ class DashboardFragment : Fragment() {
userManager = DoubtlessApp.getInstance().getAppCompRoot().getUserManager()
navigator = DoubtlessApp.getInstance().getAppCompRoot()
.getDashboardFragNavigator(requireActivity() as MainActivity)!!
-
viewModel = getViewModel()
- viewModel.fetchDoubts(forPageOne = true)
+ onCreateEventUnConsumed = true
}
override fun onCreateView(
@@ -75,6 +76,11 @@ class DashboardFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ if (onCreateEventUnConsumed) {
+ viewModel.fetchDoubts(forPageOne = true)
+ onCreateEventUnConsumed = false
+ }
+
val feedList = mutableListOf()
feedList.add(FeedEntity(FeedEntity.TYPE_USER_PROFILE, null))
@@ -82,8 +88,6 @@ class DashboardFragment : Fragment() {
adapter = GenericFeedAdapter(genericFeedEntities = feedList, onLastItemReached = {
viewModel.fetchDoubts()
}, interactionListener = object : GenericFeedAdapter.InteractionListener {
- override fun onSearchBarClicked() {
- }
override fun onDoubtClicked(doubtData: DoubtData, position: Int) {
@@ -102,6 +106,10 @@ class DashboardFragment : Fragment() {
override fun onDeleteAccountClicked() {
showBottomSheet()
}
+
+ override fun onCreatePollClicked() {}
+
+ override fun onPollOptionClicked(position: Int) {}
})
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/dashboard/viewholder/UserProfileViewHolder.kt b/app/src/main/java/com/doubtless/doubtless/screens/dashboard/viewholder/UserProfileViewHolder.kt
index f0d560b..328691c 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/dashboard/viewholder/UserProfileViewHolder.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/dashboard/viewholder/UserProfileViewHolder.kt
@@ -8,6 +8,7 @@ import com.bumptech.glide.Glide
import com.doubtless.doubtless.R
import com.doubtless.doubtless.screens.auth.usecases.UserManager
import com.doubtless.doubtless.theming.buttons.SecondaryButton
+import com.doubtless.doubtless.utils.Utils.flatten
class UserProfileViewHolder(view: View, private val interactionListener: InteractionListener) :
RecyclerView.ViewHolder(view) {
@@ -24,6 +25,7 @@ class UserProfileViewHolder(view: View, private val interactionListener: Interac
private val signOutButton: SecondaryButton
private val submitFeedback: TextView
private val deleteAccount: TextView
+ private val tvBio: TextView
init {
@@ -33,6 +35,7 @@ class UserProfileViewHolder(view: View, private val interactionListener: Interac
signOutButton = view.findViewById(R.id.btn_signout)
submitFeedback = view.findViewById(R.id.btnFeedback)
deleteAccount = view.findViewById(R.id.btn_delete_account)
+ tvBio = view.findViewById(R.id.tv_bio)
}
fun setData(userManager: UserManager) {
@@ -51,6 +54,15 @@ class UserProfileViewHolder(view: View, private val interactionListener: Interac
userName.text = userManager.getCachedUserData()!!.name
userEmail.text = userManager.getCachedUserData()!!.email
+
+ var tags = ""
+
+ userManager.getCachedUserData()!!.local_user_attr!!.tags?.forEach {
+ tags += "#$it "
+ }
+
+ tvBio.text = tags
+
Glide.with(userImage).load(userManager.getCachedUserData()!!.photoUrl).circleCrop()
.into(userImage)
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/DoubtData.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/DoubtData.kt
index cd4d8b4..861bf41 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/doubt/DoubtData.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/DoubtData.kt
@@ -64,8 +64,17 @@ data class DoubtData(
@SerializedName("is_trending")
@get:PropertyName("is_trending")
@set:PropertyName("is_trending")
- var isTrending: Boolean = false
-): Parcelable {
+ var isTrending: Boolean = false,
+ @SerializedName("xp_count")
+ @get:PropertyName("xp_count")
+ @set:PropertyName("xp_count")
+ var xpCount: Long = 0,
+ var mentorsDpWhoInteracted: List = mutableListOf(),
+ @SerializedName("iv_content")
+ @get:PropertyName("image_content_url")
+ @set:PropertyName("image_content_url")
+ var imageContentUrl: String? = null
+) : Parcelable {
companion object {
fun parse(documentSnapshot: DocumentSnapshot?): DoubtData? {
return try {
@@ -101,5 +110,7 @@ data class PublishDoubtRequest(
@SerializedName("tags")
var tags: List? = null,
@SerializedName("keywords")
- var keywords: List? = null
+ var keywords: List? = null,
+ @SerializedName("xp_count")
+ var xpCount: Long = 0
)
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/FilterTagsViewPagerAdapter.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/FilterTagsViewPagerAdapter.kt
new file mode 100644
index 0000000..c8e88ac
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/FilterTagsViewPagerAdapter.kt
@@ -0,0 +1,26 @@
+package com.doubtless.doubtless.screens.doubt
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import com.doubtless.doubtless.screens.doubt.view.ViewDoubtsFragment
+
+class FilterTagsViewPagerAdapter(
+ homeMainScreenFragment: HomeMainScreenFragment, private val tagsList: List
+) : FragmentStateAdapter(
+ homeMainScreenFragment
+) {
+
+ override fun getItemCount(): Int {
+ return tagsList.size
+ }
+
+ override fun createFragment(position: Int): Fragment {
+ val fragment = ViewDoubtsFragment()
+ val args = Bundle()
+ args.putString("tag", tagsList[position])
+ fragment.arguments = args
+ return fragment
+
+ }
+}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/HomeMainScreenFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/HomeMainScreenFragment.kt
new file mode 100644
index 0000000..6e2aeb7
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/HomeMainScreenFragment.kt
@@ -0,0 +1,115 @@
+package com.doubtless.doubtless.screens.doubt
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.doubtless.doubtless.DoubtlessApp
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.analytics.AnalyticsTracker
+import com.doubtless.doubtless.databinding.FragmentHomeMainScreenBinding
+import com.doubtless.doubtless.navigation.FragNavigator
+import com.doubtless.doubtless.screens.auth.usecases.UserManager
+import com.doubtless.doubtless.screens.doubt.view.HomeMainScreenViewModel
+import com.doubtless.doubtless.screens.main.MainActivity
+import com.google.android.material.tabs.TabLayout
+import com.google.android.material.tabs.TabLayoutMediator
+import java.util.Locale
+
+
+class HomeMainScreenFragment : Fragment() {
+ private var _binding: FragmentHomeMainScreenBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var navigator: FragNavigator
+ private lateinit var viewModel: HomeMainScreenViewModel
+ private lateinit var userManager: UserManager
+ private lateinit var analyticsTracker: AnalyticsTracker
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentHomeMainScreenBinding.inflate(inflater, container, false)
+ requireActivity().window.statusBarColor = requireContext().getColor(R.color.purple)
+ return binding.root
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ userManager = DoubtlessApp.getInstance().getAppCompRoot().getUserManager()
+ analyticsTracker = DoubtlessApp.getInstance().getAppCompRoot().getAnalyticsTracker()
+
+ val _navigator = DoubtlessApp.getInstance().getAppCompRoot()
+ .getHomeFragNavigator(requireActivity() as MainActivity)
+ if (_navigator != null) navigator = _navigator
+
+ viewModel = getViewModel()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ if (viewModel.tags.value.isNullOrEmpty()) {
+ viewModel.fetchTags()
+
+ }
+
+ viewModel.tags.observe(viewLifecycleOwner) {
+ if (binding.viewPager.adapter == null) {
+ setupViewPager(it)
+ }
+ }
+ binding.tvSearch.setOnClickListener {
+ navigator.moveToSearchFragment()
+ }
+
+ }
+
+ private fun setupViewPager(tags: List) {
+ val pagerAdapter = FilterTagsViewPagerAdapter(this@HomeMainScreenFragment, tags)
+ binding.viewPager.adapter = pagerAdapter
+ val capitalizedTagList = tags.map { s ->
+ s.replaceFirstChar {
+ if (it.isLowerCase()) it.uppercase(
+ Locale.ROOT
+ ) else it.toString()
+ }
+ }.toMutableList()
+
+ TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
+ if (position == 1) {
+ tab.text = "${userManager.getCachedUserData()?.local_user_attr!!.college!!} only"
+ return@TabLayoutMediator
+ }
+ tab.text = capitalizedTagList[position]
+ }.attach()
+
+ binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+ override fun onTabSelected(tab: TabLayout.Tab?) {
+ analyticsTracker.trackTagsFragment(capitalizedTagList[tab!!.position])
+ }
+
+ override fun onTabUnselected(tab: TabLayout.Tab?) {}
+ override fun onTabReselected(tab: TabLayout.Tab?) {}
+
+ })
+
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ private fun getViewModel(): HomeMainScreenViewModel {
+ return ViewModelProvider(
+ owner = this, factory = HomeMainScreenViewModel.Companion.Factory(
+ fetchFilterTagsUseCase = DoubtlessApp.getInstance().getAppCompRoot()
+ .getFetchFilterTagsUseCase()
+ )
+ )[HomeMainScreenViewModel::class.java]
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/create/CreateDoubtFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/create/CreateDoubtFragment.kt
index cd6c85c..9381330 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/doubt/create/CreateDoubtFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/create/CreateDoubtFragment.kt
@@ -16,11 +16,15 @@ import androidx.lifecycle.ViewModelProvider
import com.doubtless.doubtless.DoubtlessApp
import com.doubtless.doubtless.analytics.AnalyticsTracker
import com.doubtless.doubtless.databinding.FragmentCreateDoubtBinding
+import com.doubtless.doubtless.navigation.FragNavigator
+import com.doubtless.doubtless.navigation.OnBackPressListener
import com.doubtless.doubtless.screens.auth.User
import com.doubtless.doubtless.screens.auth.usecases.UserManager
import com.doubtless.doubtless.screens.doubt.PublishDoubtRequest
import com.doubtless.doubtless.screens.doubt.usecases.DoubtDataSharedPrefUseCase
import com.doubtless.doubtless.screens.doubt.usecases.PostDoubtUseCase
+import com.doubtless.doubtless.screens.main.MainActivity
+import com.doubtless.doubtless.screens.main.MainFragment
import com.doubtless.doubtless.screens.onboarding.OnBoardingAttributes
import com.doubtless.doubtless.screens.onboarding.usecases.FetchOnBoardingDataUseCase
import com.google.android.material.chip.Chip
@@ -34,6 +38,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.properties.Delegates
+/**
+ * NOTE : right now this fragment only opens up from bottom nav so the navigation functionality is coded assuming that.
+ * change things in future if this assumption changes.
+ */
class CreateDoubtFragment : Fragment() {
private var _binding: FragmentCreateDoubtBinding? = null
@@ -44,6 +52,7 @@ class CreateDoubtFragment : Fragment() {
private lateinit var analyticsTracker: AnalyticsTracker
private lateinit var postDoubtUseCase: PostDoubtUseCase
private lateinit var remoteConfig: FirebaseRemoteConfig
+ private var navigator: FragNavigator? = null
private var maxHeadingCharLimit by Delegates.notNull()
private var maxDescriptionCharLimit by Delegates.notNull()
@@ -56,20 +65,37 @@ class CreateDoubtFragment : Fragment() {
private var onBoardingAttributes: OnBoardingAttributes? = null
+ private val onBackPressListener = object : OnBackPressListener {
+ override fun onBackPress(): Boolean {
+
+ val backPressConsumed = navigator?.onBackPress() ?: false
+
+ return if (backPressConsumed) true
+ else {
+ (parentFragment as? MainFragment)?.selectHomeBottomNavElement()
+ true
+ }
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
db = Firebase.firestore
- userManager = DoubtlessApp.getInstance().getAppCompRoot().getUserManager()
- analyticsTracker = DoubtlessApp.getInstance().getAppCompRoot().getAnalyticsTracker()
- doubtDataSharedPrefUseCase =
- DoubtlessApp.getInstance().getAppCompRoot().getDoubtDataSharedPrefUseCase()
- onBoardingDataUseCase = DoubtlessApp.getInstance().getAppCompRoot()
- .getFetchOnBoardingDataUseCase(userManager.getCachedUserData()!!)
- remoteConfig = DoubtlessApp.getInstance().getAppCompRoot().getRemoteConfig()
- postDoubtUseCase = DoubtlessApp.getInstance().getAppCompRoot().getPostDoubtUseCase()
+
+ val appComp = DoubtlessApp.getInstance().getAppCompRoot()
+
+ userManager = appComp.getUserManager()
+ analyticsTracker = appComp.getAnalyticsTracker()
+ doubtDataSharedPrefUseCase = appComp.getDoubtDataSharedPrefUseCase()
+ onBoardingDataUseCase =
+ appComp.getFetchOnBoardingDataUseCase(userManager.getCachedUserData()!!)
+ remoteConfig = appComp.getRemoteConfig()
+ postDoubtUseCase = appComp.getPostDoubtUseCase()
+ navigator = appComp.getCreateFragmentNavigator(requireActivity() as MainActivity)
+
viewModel = ViewModelProvider(
- this,
- CreateDoubtViewModel.Companion.Factory(postDoubtUseCase)
+ this, CreateDoubtViewModel.Companion.Factory(postDoubtUseCase)
)[CreateDoubtViewModel::class.java]
}
@@ -98,11 +124,8 @@ class CreateDoubtFragment : Fragment() {
} else {
isButtonClicked = false
binding.postButton.alpha = 1f
- Toast.makeText(
- /* context = */ context,
- /* text = */
- "${(result as CreateDoubtViewModel.Result.Error).message}",
- /* duration = */
+ Toast.makeText(/* context = */ context,/* text = */
+ "${(result as CreateDoubtViewModel.Result.Error).message}",/* duration = */
Toast.LENGTH_SHORT
).show()
}
@@ -175,14 +198,19 @@ class CreateDoubtFragment : Fragment() {
}
+ override fun onResume() {
+ super.onResume()
+ (requireActivity() as MainActivity).registerBackPress(onBackPressListener)
+ }
+
override fun onPause() {
super.onPause()
doubtDataSharedPrefUseCase.saveDoubtData(
binding.doubtHeading.text.toString(), binding.doubtDescription.text.toString()
)
+ (requireActivity() as MainActivity).unregisterBackPress(onBackPressListener)
}
-
private fun checkText() {
val errorMessage = isEverythingValid()
@@ -199,7 +227,6 @@ class CreateDoubtFragment : Fragment() {
binding.postButton.alpha = 0.8f
showConfirmationDialog(getSelectedTags(), keywordsEntered)
-
}
private fun isEverythingValid(): String? {
@@ -221,11 +248,9 @@ class CreateDoubtFragment : Fragment() {
val size = getSelectedTags().size
- if (size > 3)
- return "More than 3 Tags are not allowed!"
+ if (size > 3) return "More than 3 Tags are not allowed!"
- if (size == 0)
- return "Please select a tag!"
+ if (size == 0) return "Please select a tag!"
return null
}
@@ -247,7 +272,8 @@ class CreateDoubtFragment : Fragment() {
createDoubt(
binding.doubtHeading.text.toString(),
binding.doubtDescription.text.toString(),
- tags, keywords,
+ tags,
+ keywords,
userManager.getCachedUserData()!!
)
dialogInterface.dismiss()
@@ -265,8 +291,7 @@ class CreateDoubtFragment : Fragment() {
val jsonObject = Gson().fromJson(maxCharLimit, Map::class.java) as Map<*, *>
maxHeadingCharLimit = (jsonObject["max_heading_char_limit"] as Double).toInt()
- maxDescriptionCharLimit =
- (jsonObject["max_description_char_limit"] as Double).toInt()
+ maxDescriptionCharLimit = (jsonObject["max_description_char_limit"] as Double).toInt()
maxKeywordsLimit = (jsonObject["keywords_limit"] as Double).toInt()
setMaxCharacterLimit(maxHeadingCharLimit, maxDescriptionCharLimit, maxKeywordsLimit)
@@ -277,9 +302,7 @@ class CreateDoubtFragment : Fragment() {
}
private fun setMaxCharacterLimit(
- maxHeadingCharLimit: Int,
- maxDescriptionCharLimit: Int,
- maxKeywordsLimit: Int
+ maxHeadingCharLimit: Int, maxDescriptionCharLimit: Int, maxKeywordsLimit: Int
) {
binding.doubtHeading.filters =
arrayOf(InputFilter.LengthFilter(maxHeadingCharLimit))
@@ -288,11 +311,7 @@ class CreateDoubtFragment : Fragment() {
}
private fun createDoubt(
- heading: String,
- description: String,
- tags: List,
- keywords: List,
- user: User
+ heading: String, description: String, tags: List, keywords: List, user: User
) {
viewModel.postDoubt(
PublishDoubtRequest(
@@ -305,7 +324,8 @@ class CreateDoubtFragment : Fragment() {
description = description,
netVotes = 0f,
tags = tags,
- keywords = keywords
+ keywords = keywords,
+ xpCount = user.xpCount
)
)
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchDoubtDataFromDoubtIdUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchDoubtDataFromDoubtIdUseCase.kt
new file mode 100644
index 0000000..a010ec1
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchDoubtDataFromDoubtIdUseCase.kt
@@ -0,0 +1,27 @@
+package com.doubtless.doubtless.screens.doubt.usecases
+
+import com.doubtless.doubtless.constants.FirestoreCollection
+import com.doubtless.doubtless.screens.doubt.DoubtData
+import com.google.firebase.firestore.FirebaseFirestore
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
+
+class FetchDoubtDataFromDoubtIdUseCase constructor(
+ private val firestore: FirebaseFirestore
+) {
+
+ suspend fun getDoubtData(doubtId: String): DoubtData? = withContext(Dispatchers.IO) {
+ try {
+ val document = firestore.collection(FirestoreCollection.AllDoubts)
+ .whereEqualTo("doubt_id", doubtId)
+ .get().await()
+
+ return@withContext DoubtData.parse(document.documents[0])
+
+ } catch (e: Exception) {
+ return@withContext null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchFilterTagsUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchFilterTagsUseCase.kt
new file mode 100644
index 0000000..22de2c2
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchFilterTagsUseCase.kt
@@ -0,0 +1,45 @@
+package com.doubtless.doubtless.screens.doubt.usecases
+
+import com.doubtless.doubtless.constants.FirestoreCollection
+import com.google.firebase.firestore.FirebaseFirestore
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
+
+class FetchFilterTagsUseCase constructor(
+ private val firestore: FirebaseFirestore,
+) {
+ private var cachedTags: List? = null
+
+ sealed class Result {
+ class Success(val data: List) : Result()
+ class Error(val message: String) : Result()
+ }
+
+ suspend fun fetchTagsFromFirebase(): Result = withContext(Dispatchers.IO) {
+ return@withContext try {
+ if (cachedTags?.isNotEmpty() == true) {
+ Result.Success(cachedTags!!)
+ } else {
+ val tags: MutableList = mutableListOf()
+
+ val querySnapshot = firestore.collection(FirestoreCollection.MiscAppData)
+ .whereEqualTo("type", "attr_data").get().await()
+
+ for (document in querySnapshot.documents) {
+ val tagsList = document.get("tags") as? List
+ tagsList?.let {
+ tags.addAll(it)
+ }
+ }
+
+ cachedTags = tags
+ Result.Success(tags)
+ }
+
+ } catch (e: Exception) {
+ Result.Error(e.message ?: "Failed to fetch tags")
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchInteractedMentorDataForDoubt.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchInteractedMentorDataForDoubt.kt
new file mode 100644
index 0000000..f99724d
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/usecases/FetchInteractedMentorDataForDoubt.kt
@@ -0,0 +1,36 @@
+package com.doubtless.doubtless.screens.doubt.usecases
+
+import com.doubtless.doubtless.constants.FirestoreCollection
+import com.doubtless.doubtless.screens.answers.AnswerData
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.Query
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
+
+class FetchInteractedMentorDataForDoubt constructor(
+ private val firestore: FirebaseFirestore
+) {
+
+ suspend fun fetch(doubtId: String): List = withContext(Dispatchers.IO) {
+ return@withContext try {
+
+ val docx = firestore.collection(FirestoreCollection.AllDoubts)
+ .document(doubtId)
+ .collection(FirestoreCollection.DoubtAnswer)
+ .whereGreaterThanOrEqualTo("xp_count", 1000)
+ .orderBy("xp_count", Query.Direction.DESCENDING)
+ .limit(5)
+ .get().await()
+
+ val dps = AnswerData.parse(docx)?.map {
+ it.authorPhotoUrl ?: ""
+ }
+
+ dps ?: listOf()
+
+ } catch (e: Exception) {
+ listOf()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/HomeMainScreenViewModel.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/HomeMainScreenViewModel.kt
new file mode 100644
index 0000000..7e15713
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/HomeMainScreenViewModel.kt
@@ -0,0 +1,51 @@
+package com.doubtless.doubtless.screens.doubt.view
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.doubtless.doubtless.constants.FirestoreCollection
+import com.doubtless.doubtless.screens.doubt.usecases.FetchFilterTagsUseCase
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class HomeMainScreenViewModel constructor(
+ private val fetchFilterTagsUseCase: FetchFilterTagsUseCase,
+) : ViewModel() {
+
+ private val _tags = MutableLiveData>()
+ val tags: LiveData> = _tags
+
+ private val startTags = listOf(FirestoreCollection.TAG_ALL, FirestoreCollection.TAG_MY_COLLEGE)
+
+ fun fetchTags() = viewModelScope.launch(Dispatchers.IO) {
+
+ val result = fetchFilterTagsUseCase.fetchTagsFromFirebase()
+
+ if (result is FetchFilterTagsUseCase.Result.Error) {
+ _tags.postValue(startTags)
+ return@launch
+ }
+
+ result as FetchFilterTagsUseCase.Result.Success
+
+ val tags = result.data.toMutableList()
+ tags.addAll(
+ 0, startTags
+ )
+ _tags.postValue(tags)
+ }
+
+
+ companion object {
+ class Factory constructor(
+ private val fetchFilterTagsUseCase: FetchFilterTagsUseCase
+ ) : ViewModelProvider.Factory {
+
+ override fun create(modelClass: Class): T {
+ return HomeMainScreenViewModel(fetchFilterTagsUseCase) as T
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsFragment.kt
index 439e3e7..0034a17 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsFragment.kt
@@ -1,25 +1,27 @@
package com.doubtless.doubtless.screens.doubt.view
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import com.doubtless.doubtless.DoubtlessApp
-import com.doubtless.doubtless.R
import com.doubtless.doubtless.analytics.AnalyticsTracker
import com.doubtless.doubtless.databinding.FragmentViewDoubtsBinding
import com.doubtless.doubtless.navigation.FragNavigator
-import com.doubtless.doubtless.screens.common.GenericFeedAdapter
+import com.doubtless.doubtless.screens.auth.exception.UserNotFoundException
+import com.doubtless.doubtless.screens.auth.login.LoginUtilsImpl
import com.doubtless.doubtless.screens.auth.usecases.UserManager
+import com.doubtless.doubtless.screens.common.GenericFeedAdapter
import com.doubtless.doubtless.screens.doubt.DoubtData
import com.doubtless.doubtless.screens.home.entities.FeedConfig
import com.doubtless.doubtless.screens.main.MainActivity
-import com.google.firebase.firestore.FirebaseFirestore
+import com.doubtless.doubtless.utils.Resource
+import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.gson.Gson
@@ -35,34 +37,30 @@ class ViewDoubtsFragment : Fragment() {
private lateinit var navigator: FragNavigator
private lateinit var remoteConfig: FirebaseRemoteConfig
private lateinit var feedConfig: FeedConfig
- private lateinit var firebaseFirestore: FirebaseFirestore
+ private lateinit var tag: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = TransitionInflater.from(requireContext())
- //enterTransition = inflater.inflateTransition(R.transition.slide)
+ // enterTransition = inflater.inflateTransition(R.transition.slide)
// exitTransition = inflater.inflateTransition(R.transition.fade)
+ tag = arguments?.getString("tag")!!
+
userManager = DoubtlessApp.getInstance().getAppCompRoot().getUserManager()
analyticsTracker = DoubtlessApp.getInstance().getAppCompRoot().getAnalyticsTracker()
remoteConfig = DoubtlessApp.getInstance().getAppCompRoot().getRemoteConfig()
- feedConfig = FeedConfig.parse(Gson(), remoteConfig)
- ?: FeedConfig(
- pageSize = 10,
- recentPostsCount = 6,
- feedDebounce = 3000,
- searchDebounce = 600
- )
+ feedConfig = FeedConfig.parse(Gson(), remoteConfig) ?: FeedConfig(
+ pageSize = 10, recentPostsCount = 6, feedDebounce = 3000, searchDebounce = 600
+ )
val _navigator = DoubtlessApp.getInstance().getAppCompRoot()
.getHomeFragNavigator(requireActivity() as MainActivity)
- if (_navigator != null)
- navigator = _navigator
+ if (_navigator != null) navigator = _navigator
viewModel = getViewModel()
- viewModel.fetchDoubts(forPageOne = true)
}
override fun onCreateView(
@@ -75,67 +73,106 @@ class ViewDoubtsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ if (viewModel.homeEntities.isEmpty()) {
+ viewModel.fetchDoubts(forPageOne = true, feedTag = tag)
+ }
+
// for debouncing
var lastRefreshed = System.currentTimeMillis()
- binding.llProgressBar.visibility= View.VISIBLE //show progress bar
+ binding.llProgressBar.visibility = View.VISIBLE //show progress bar
binding.layoutSwipe.setOnRefreshListener {
if (System.currentTimeMillis() - lastRefreshed < feedConfig.feedDebounce) {
- Log.d("feed debounced", feedConfig.feedDebounce.toString())
binding.layoutSwipe.isRefreshing = false
return@setOnRefreshListener
}
lastRefreshed = System.currentTimeMillis()
-
binding.layoutSwipe.isRefreshing = true
- viewModel.refreshList()
+ viewModel.refreshList(tag = tag)
adapter.clearCurrentList()
}
- adapter = GenericFeedAdapter(
- genericFeedEntities = viewModel.homeEntities.toMutableList(),
- onLastItemReached = {
- viewModel.fetchDoubts()
- },
- interactionListener = object : GenericFeedAdapter.InteractionListener {
- override fun onSearchBarClicked() {
- navigator.moveToSearchFragment()
- }
+ if (!::adapter.isInitialized) {
+ adapter =
+ GenericFeedAdapter(genericFeedEntities = viewModel.homeEntities.toMutableList(),
+ onLastItemReached = {
+ viewModel.fetchDoubts(feedTag = tag)
+ },
+ interactionListener = object : GenericFeedAdapter.InteractionListener {
- override fun onDoubtClicked(doubtData: DoubtData, position: Int) {
- // note that this we are not sending a copy of doubtData here,
- // hence if netVotes are changed on the other screen then it will change here too.
- // this solves our problem but can cause complications on long term.
- navigator.moveToDoubtDetailFragment(doubtData)
- }
+ override fun onDoubtClicked(doubtData: DoubtData, position: Int) {
+ // note that this we are not sending a copy of doubtData here,
+ // hence if netVotes are changed on the other screen then it will change here too.
+ // this solves our problem but can cause complications on long term.
+ navigator.moveToDoubtDetailFragment(doubtData)
+ }
- override fun onSignOutClicked() {
+ override fun onSignOutClicked() {
- }
+ }
- override fun onSubmitFeedbackClicked() {
- }
+ override fun onSubmitFeedbackClicked() {
+ }
+
+ override fun onDeleteAccountClicked() {
+ }
+
+ override fun onCreatePollClicked() {
+ navigator.moveToCreatePollFragment()
+ }
+
+ override fun onPollOptionClicked(position: Int) {
+ }
+ })
+ }
- override fun onDeleteAccountClicked() {
- }
- })
// how is rv restoring its scroll pos when switching tabs?
binding.doubtsRecyclerView.adapter = adapter
binding.doubtsRecyclerView.layoutManager = LinearLayoutManager(context)
- viewModel.fetchedHomeEntities.observe(viewLifecycleOwner) {
- binding.llProgressBar.visibility= View.GONE //hide progress bar
- if (it == null) return@observe
- adapter.appendDoubts(it)
- viewModel.notifyFetchedDoubtsConsumed()
+ viewModel.fetchedHomeEntities.observe(viewLifecycleOwner) { result ->
+
+ binding.llProgressBar.visibility = View.GONE //hide progress bar
binding.layoutSwipe.isRefreshing = false
+
+ if (result == null) return@observe
+
+ when (result) {
+
+ is Resource.Success<*> -> {
+ if (result.data == null) return@observe
+ adapter.appendDoubts(result.data)
+ viewModel.notifyFetchedDoubtsConsumed()
+ }
+
+ is Resource.Error<*> -> {
+
+ val message = result.message ?: "some error occurred!"
+
+ if (result.error is UserNotFoundException) {
+ FirebaseCrashlytics.getInstance()
+ .recordException(Exception("current user is null"))
+
+ LoginUtilsImpl.logOutUser(analyticsTracker, requireActivity())
+ }
+
+ showToast(message)
+ }
+
+ is Resource.Loading<*> -> {}
+ }
}
}
+ private fun showToast(msg: String) {
+ Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT)
+ .show()
+ }
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
@@ -143,8 +180,7 @@ class ViewDoubtsFragment : Fragment() {
private fun getViewModel(): ViewDoubtsViewModel {
return ViewModelProvider(
- owner = this,
- factory = ViewDoubtsViewModel.Companion.Factory(
+ owner = this, factory = ViewDoubtsViewModel.Companion.Factory(
fetchHomeFeedUseCase = DoubtlessApp.getInstance().getAppCompRoot()
.getFetchHomeFeedUseCase(feedConfig),
analyticsTracker = analyticsTracker,
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsViewModel.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsViewModel.kt
index 67c90aa..c07f714 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsViewModel.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/ViewDoubtsViewModel.kt
@@ -1,18 +1,28 @@
package com.doubtless.doubtless.screens.doubt.view
-import androidx.lifecycle.*
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.doubtless.doubtless.DoubtlessApp
+import com.doubtless.doubtless.R
import com.doubtless.doubtless.analytics.AnalyticsTracker
+import com.doubtless.doubtless.screens.auth.exception.UserNotFoundException
import com.doubtless.doubtless.screens.auth.usecases.UserManager
import com.doubtless.doubtless.screens.home.entities.FeedEntity
import com.doubtless.doubtless.screens.home.usecases.FetchHomeFeedUseCase
-import com.doubtless.doubtless.screens.home.usecases.FetchHomeFeedUseCase.*
+import com.doubtless.doubtless.screens.home.usecases.FetchHomeFeedUseCase.FetchHomeFeedRequest
+import com.doubtless.doubtless.screens.home.usecases.FetchHomeFeedUseCase.Result
+import com.doubtless.doubtless.utils.Resource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlin.collections.set
class ViewDoubtsViewModel constructor(
private val fetchHomeFeedUseCase: FetchHomeFeedUseCase,
private val analyticsTracker: AnalyticsTracker,
- private val userManager: UserManager
+ private val userManager: UserManager,
) : ViewModel() {
private val _homeEntities = mutableListOf()
@@ -22,81 +32,108 @@ class ViewDoubtsViewModel constructor(
private var isLoading = false
- private val _fetchedHomeEntities = MutableLiveData?>()
- val fetchedHomeEntities: LiveData?> =
+ private val _fetchedHomeEntities = MutableLiveData?>>()
+ val fetchedHomeEntities: LiveData?>> =
_fetchedHomeEntities // TODO : use Result here!
fun notifyFetchedDoubtsConsumed() {
- _fetchedHomeEntities.value = null
+ _fetchedHomeEntities.value = Resource.Success(data = null)
}
- fun fetchDoubts(forPageOne: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
+ fun fetchDoubts(forPageOne: Boolean = false, feedTag: String) =
+ viewModelScope.launch(Dispatchers.IO) {
- if (isLoading) return@launch
+ if (isLoading) return@launch
- isLoading = true
+ isLoading = true
- val result = fetchHomeFeedUseCase.fetchFeedSync(
- request = FetchHomeFeedRequest(
- user = userManager.getCachedUserData()!!,
- fetchFromPage1 = forPageOne
+ val currentUser = userManager.getCachedUserData() ?: userManager.getLoggedInUser()
+
+ if (currentUser == null) {
+ // ERROR CASE
+ _fetchedHomeEntities.postValue(
+ Resource.Error(
+ message = DoubtlessApp.getInstance().getString(R.string.sign_in_again),
+ data = null,
+ error = UserNotFoundException()
+ )
+ )
+ isLoading = false
+
+ return@launch
+ }
+
+ val result = fetchHomeFeedUseCase.fetchFeedSync(
+ request = FetchHomeFeedRequest(
+ user = currentUser,
+ fetchFromPage1 = forPageOne,
+ tag = feedTag
+ )
)
- )
- if (result is Result.ListEnded || result is Result.Error) {
- // ERROR CASE
- _fetchedHomeEntities.postValue(null)
- isLoading = false
- return@launch
- }
+ if (result is Result.ListEnded || result is Result.Error) {
+ // ERROR CASE
+ _fetchedHomeEntities.postValue(Resource.Error())
+ isLoading = false
+ return@launch
+ }
- result as FetchHomeFeedUseCase.Result.Success
+ result as Result.Success
- if (!forPageOne) {
- analyticsTracker.trackFeedNextPage(homeEntities.size)
- } else {
- analyticsTracker.trackFeedRefresh()
- }
+ if (!forPageOne) {
+ analyticsTracker.trackFeedNextPage(homeEntities.size)
+ } else {
+ analyticsTracker.trackFeedRefresh()
+ }
- val entitiesFromServer = mutableListOf()
+ val entitiesFromServer = mutableListOf()
- result.data.forEach { doubtData ->
+ val pollOptions = listOf("Krishna", "Sneha", "Dhiraj")
+ if (_homeEntities.isEmpty()) {
+ entitiesFromServer.add(FeedEntity.getOptionButtons())
+ entitiesFromServer.add(FeedEntity.getPollEntity(pollOptions))
+ }
+
+
+ result.data.forEach { doubtData ->
+ _homeEntities.addAll(entitiesFromServer)
+ _fetchedHomeEntities.postValue(Resource.Success(entitiesFromServer))
+ fetchHomeFeedUseCase.notifyDistinctDocsFetched(
+ docsFetched = homeEntities.size
+ )
- // we got the data for page 2 (lets say) now check if these posts existed on page 1 and add only unique ones.
- if (_homeEntitiesIds.contains(doubtData.id) == false) {
- entitiesFromServer.add(doubtData.toHomeEntity())
- _homeEntitiesIds[doubtData.id!!] = 1
+ isLoading = false
}
- }
- // for page 1 call add search entity
- if (_homeEntities.isEmpty())
- entitiesFromServer.add(0, FeedEntity.getSearchEntity())
-
- _homeEntities.addAll(entitiesFromServer)
- _fetchedHomeEntities.postValue(entitiesFromServer)
- fetchHomeFeedUseCase.notifyDistinctDocsFetched(
- docsFetched = homeEntities.size
- - /* subtract one for search entity, ideally should have counted Type = Doubt size */ 1
- )
- isLoading = false
- }
- fun refreshList() {
+ _homeEntities.addAll(entitiesFromServer)
+ _fetchedHomeEntities.postValue(Resource.Success(entitiesFromServer))
+ fetchHomeFeedUseCase.notifyDistinctDocsFetched(
+ docsFetched = homeEntities.size
+ - /* subtract one for search entity, ideally should have counted Type = Doubt size */ 1
+ )
+ isLoading = false
+ }
+
+ fun refreshList(tag: String?) {
_homeEntities.clear()
_homeEntitiesIds.clear()
- fetchDoubts(forPageOne = true)
+ fetchDoubts(forPageOne = true, feedTag = tag!!)
}
companion object {
class Factory constructor(
private val fetchHomeFeedUseCase: FetchHomeFeedUseCase,
private val analyticsTracker: AnalyticsTracker,
- private val userManager: UserManager
+ private val userManager: UserManager,
) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
- return ViewDoubtsViewModel(fetchHomeFeedUseCase, analyticsTracker, userManager) as T
+ return ViewDoubtsViewModel(
+ fetchHomeFeedUseCase,
+ analyticsTracker,
+ userManager
+ ) as T
}
}
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/viewholder/DoubtPreviewViewHolder.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/viewholder/DoubtPreviewViewHolder.kt
index 2d37b9b..367066f 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/viewholder/DoubtPreviewViewHolder.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/viewholder/DoubtPreviewViewHolder.kt
@@ -1,26 +1,27 @@
package com.doubtless.doubtless.screens.doubt.view.viewholder
-import android.content.res.ColorStateList
import android.text.util.Linkify
-import android.util.Log
import android.view.View
+import android.widget.CheckBox
import android.widget.ImageView
+import android.widget.LinearLayout
import android.widget.TextView
-import android.widget.Toast
-import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.doubtless.doubtless.DoubtlessApp
import com.doubtless.doubtless.R
+import com.doubtless.doubtless.constants.GamificationConstants
import com.doubtless.doubtless.screens.doubt.DoubtData
import com.doubtless.doubtless.screens.doubt.usecases.VotingUseCase
import com.doubtless.doubtless.utils.Utils
import com.doubtless.doubtless.utils.Utils.flatten
+import com.doubtless.doubtless.utils.Utils.toPx
+import com.doubtless.doubtless.utils.addStateListAnimation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import java.util.*
+import java.util.Date
import kotlin.math.floor
class DoubtPreviewViewHolder(
@@ -40,31 +41,33 @@ class DoubtPreviewViewHolder(
private val description: TextView
private val ivDp: ImageView
private val tvNetVotes: TextView
- private val ivUpvotes: ImageView
- private val ivDownvotes: ImageView
+ private val upvotes: CheckBox
+ private val downvotes: CheckBox
private val tvAnswers: TextView
private val tvTags: TextView
private val tvCollege: TextView
private val tvYear: TextView = itemView.findViewById(R.id.user_year)
- private val icFire: ImageView = itemView.findViewById(R.id.ic_fire)
+ private val userBadge: ImageView = itemView.findViewById(R.id.user_badge)
+ private val llMentorsDp: LinearLayout = itemView.findViewById(R.id.ll_answered_mentor)
+ private val ivContent: ImageView = itemView.findViewById(R.id.iv_content)
private val analyticsTracker = DoubtlessApp.getInstance().getAppCompRoot().getAnalyticsTracker()
init {
userName = view.findViewById(R.id.tv_username)
time = view.findViewById(R.id.author_doubt_timestamp)
- heading = view.findViewById(R.id.user_doubt_heading)
- description = view.findViewById(R.id.user_doubt_description)
+ heading = view.findViewById(R.id.tv_poll_heading)
+ description = view.findViewById(R.id.tv_poll_description)
ivDp = view.findViewById(R.id.iv_dp)
tvNetVotes = view.findViewById(R.id.tv_votes)
tvAnswers = view.findViewById(R.id.tv_answers)
tvTags = view.findViewById(R.id.tv_tags)
tvCollege = view.findViewById(R.id.user_college)
- ivUpvotes = view.findViewById(R.id.iv_upvotes)
- ivDownvotes = view.findViewById(R.id.iv_downvote)
+ upvotes = view.findViewById(R.id.cb_upvotes)
+ downvotes = view.findViewById(R.id.cb_downvote)
- ivUpvotes.isVisible = showVotingLayout
- ivDownvotes.isVisible = showVotingLayout
+ upvotes.isVisible = showVotingLayout
+ downvotes.isVisible = showVotingLayout
}
fun setData(doubtData: DoubtData) {
@@ -92,30 +95,62 @@ class DoubtPreviewViewHolder(
tvNetVotes.text = kotlin.math.floor(doubtData.netVotes).toInt().toString()
- tvYear.text = "| ${doubtData.year} Year |"
+ if (doubtData.year.equals("passout", ignoreCase = true)) {
+ tvYear.text = "| ${doubtData.year} |"
+ } else {
+ tvYear.text = "| ${doubtData.year} Year |"
+ }
description.text = doubtData.description?.trim()
description.isVisible = !doubtData.description.isNullOrEmpty()
tvAnswers.text = doubtData.no_answers.toString()
- if (!doubtData.tags.isNullOrEmpty())
- tvTags.text = "Related to : " + doubtData.tags!!.flatten()
- else
+ if (!doubtData.tags.isNullOrEmpty()) {
+ var tags = ""
+
+ doubtData.tags?.forEach {
+ tags += "#$it "
+ }
+
+ tvTags.text = tags
+
+ } else
tvTags.isVisible = false
Glide.with(ivDp).load(doubtData.userPhotoUrl).circleCrop()
.into(ivDp)
- icFire.isVisible = doubtData.isTrending
+ // image content
+ if (!doubtData.imageContentUrl.isNullOrEmpty()) {
+
+ ivContent.isVisible = true
+
+ Glide.with(itemView.context)
+ .load(doubtData.imageContentUrl)
+ .into(ivContent)
+
+ } else {
+ ivContent.isVisible = false
+ }
+
+ userBadge.isVisible = doubtData.xpCount > GamificationConstants.MENTOR_XP_THRESHOLD
+
+
+ setupMentorsWhoInteractedDpUi(doubtData)
+
+ // voting ui
val votingUseCase = DoubtlessApp.getInstance().getAppCompRoot()
.getDoubtVotingDoubtCase(doubtData.copy())
setVotesUi(doubtData, votingUseCase)
- ivUpvotes.setOnClickListener {
+
+ upvotes.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
+ if (it.stateListAnimator == null)
+ it.addStateListAnimation(R.animator.scale_votes_icon)
val result = votingUseCase.upvoteDoubt()
@@ -130,8 +165,10 @@ class DoubtPreviewViewHolder(
}
}
- ivDownvotes.setOnClickListener {
+ downvotes.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
+ if (it.stateListAnimator == null)
+ it.addStateListAnimation(R.animator.scale_votes_icon)
val result = votingUseCase.downVoteDoubt()
@@ -147,24 +184,55 @@ class DoubtPreviewViewHolder(
}
}
- private fun setVotesUi(doubtData: DoubtData, votingUseCase: VotingUseCase) {
- ivDownvotes.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_off_alt_24))
+ private fun setupMentorsWhoInteractedDpUi(doubtData: DoubtData) {
- ivUpvotes.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_off_alt_24))
- // ivUpvotes.setColorFilter(ContextCompat.getColor(itemView.context, R.color.grey), android.graphics.PorterDuff.Mode.SRC_IN);
+ // reset view state first!
+ val dpsCount = llMentorsDp.childCount - 3
- tvNetVotes.text = floor(doubtData.netVotes).toInt().toString()
+ if (dpsCount > 0) {
+ repeat(dpsCount) {
+ llMentorsDp.removeView(llMentorsDp.getChildAt(it + 3)) // 3,4,5..
+ }
+ }
+
+ // then setup ui
+ doubtData.mentorsDpWhoInteracted.forEach {
+ if (it.isEmpty()) return@forEach
+
+ val view = ImageView(itemView.context)
+ view.layoutParams = LinearLayout.LayoutParams(22.toPx().toInt(), 22.toPx().toInt())
+ .apply {
+ this.marginStart = 4.toPx().toInt()
+ }
+
+ Glide.with(itemView.context).load(it).circleCrop().into(view)
+ llMentorsDp.addView(view)
+ }
+
+ llMentorsDp.isVisible = !doubtData.mentorsDpWhoInteracted.isEmpty()
+
+ }
+
+ private fun setVotesUi(doubtData: DoubtData, votingUseCase: VotingUseCase) {
+ tvNetVotes.text = floor(doubtData.netVotes).toInt().toString()
CoroutineScope(Dispatchers.Main).launch {
- val currentState = votingUseCase.getUserCurrentState()
+ when (votingUseCase.getUserCurrentState()) {
+ VotingUseCase.UPVOTED -> {
+ downvotes.isClickable = false
+ upvotes.isChecked = true
+ }
- if (currentState == VotingUseCase.UPVOTED) {
- ivUpvotes.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_filled))
- // ivUpvotes.setColorFilter(ContextCompat.getColor(itemView.context, R.color.purple), android.graphics.PorterDuff.Mode.SRC_IN);
- }
+ VotingUseCase.DOWNVOTED -> {
+ upvotes.isClickable = false
+ downvotes.isChecked = true
+ }
- if (currentState == VotingUseCase.DOWNVOTED)
- ivDownvotes.setImageDrawable(itemView.context.getDrawable(R.drawable.ic_baseline_thumb_up_filled))
+ else -> {
+ downvotes.isClickable = true
+ upvotes.isClickable = true
+ }
+ }
}
}
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/viewholder/PollsViewHolder.kt b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/viewholder/PollsViewHolder.kt
new file mode 100644
index 0000000..1117e76
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/doubt/view/viewholder/PollsViewHolder.kt
@@ -0,0 +1,41 @@
+package com.doubtless.doubtless.screens.doubt.view.viewholder
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.doubtless.doubtless.R
+
+class PollsViewHolder constructor(
+ private val view: View
+) : ViewHolder(view) {
+
+ private val userName: TextView
+ private val time: TextView
+ private val heading: TextView
+ private val ivDp: ImageView
+ private val tvNetVotes: TextView
+ private val upvotes: CheckBox
+ private val downvotes: CheckBox
+ private val tvAnswers: TextView
+ private val tvCollege: TextView
+ private val tvYear: TextView = itemView.findViewById(R.id.user_year)
+ private val userBadge: ImageView = itemView.findViewById(R.id.user_badge)
+
+ init {
+ userName = view.findViewById(R.id.tv_username)
+ time = view.findViewById(R.id.author_doubt_timestamp)
+ heading = view.findViewById(R.id.user_doubt_heading)
+ ivDp = view.findViewById(R.id.iv_dp)
+ tvNetVotes = view.findViewById(R.id.tv_votes)
+ tvAnswers = view.findViewById(R.id.tv_answers)
+ tvCollege = view.findViewById(R.id.user_college)
+ upvotes = view.findViewById(R.id.cb_upvotes)
+ downvotes = view.findViewById(R.id.cb_downvote)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/home/HomeFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/home/HomeFragment.kt
index f1fc215..c2db25f 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/home/HomeFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/home/HomeFragment.kt
@@ -12,7 +12,7 @@ import com.doubtless.doubtless.R
import com.doubtless.doubtless.databinding.FragmentHomeBinding
import com.doubtless.doubtless.navigation.FragNavigator
import com.doubtless.doubtless.navigation.OnBackPressListener
-import com.doubtless.doubtless.screens.doubt.view.ViewDoubtsFragment
+import com.doubtless.doubtless.screens.doubt.HomeMainScreenFragment
import com.doubtless.doubtless.screens.main.MainActivity
class HomeFragment : Fragment() {
@@ -21,6 +21,7 @@ class HomeFragment : Fragment() {
private lateinit var navigator: FragNavigator
private var _binding: FragmentHomeBinding? = null
+
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
@@ -30,7 +31,7 @@ class HomeFragment : Fragment() {
if (savedInstanceState == null) {
childFragmentManager.commit {
- replace(R.id.bottomNav_child_container, ViewDoubtsFragment())
+ replace(R.id.bottomNav_child_container, HomeMainScreenFragment())
}
}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/home/entities/FeedEntity.kt b/app/src/main/java/com/doubtless/doubtless/screens/home/entities/FeedEntity.kt
index 1c455b8..9f90963 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/home/entities/FeedEntity.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/home/entities/FeedEntity.kt
@@ -6,16 +6,21 @@ import com.doubtless.doubtless.screens.search.SearchResult
data class FeedEntity(
val type: Int,
val doubt: DoubtData? = null,
- val search_doubt: SearchResult? = null
+ val search_doubt: SearchResult? = null,
+ val pollOptions: List? = null
) {
companion object {
const val TYPE_DOUBT = 1
- const val TYPE_SEARCH = 2
- const val TYPE_SEARCH_RESULT = 3
- const val TYPE_USER_PROFILE = 4
+ const val TYPE_SEARCH_RESULT = 2
+ const val TYPE_USER_PROFILE = 3
+ const val TYPE_BUTTONS = 4
+ const val TYPE_POLL = 5
- fun getSearchEntity(): FeedEntity {
- return FeedEntity(TYPE_SEARCH, null, null)
+ fun getOptionButtons(): FeedEntity{
+ return FeedEntity(TYPE_BUTTONS, null, null)
+ }
+ fun getPollEntity(pollOptions: List): FeedEntity {
+ return FeedEntity(TYPE_POLL, null, null, pollOptions)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByDateUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByDateUseCase.kt
index 1f57813..55fe491 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByDateUseCase.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByDateUseCase.kt
@@ -2,7 +2,7 @@ package com.doubtless.doubtless.screens.home.usecases
import com.doubtless.doubtless.constants.FirestoreCollection
import com.doubtless.doubtless.screens.doubt.DoubtData
-import com.google.firebase.firestore.FieldPath
+import com.doubtless.doubtless.screens.doubt.usecases.FetchInteractedMentorDataForDoubt
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import com.google.firebase.firestore.QuerySnapshot
@@ -11,6 +11,7 @@ import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
class FetchFeedByDateUseCase constructor(
+ private val fetchInteractedMentorDataForDoubt: FetchInteractedMentorDataForDoubt,
private val firestore: FirebaseFirestore
) {
@@ -25,8 +26,25 @@ class FetchFeedByDateUseCase constructor(
withContext(Dispatchers.IO) {
try {
- var query = firestore.collection(FirestoreCollection.AllDoubts)
- .orderBy("created_on", Query.Direction.DESCENDING)
+
+ var query = when (request.tag) {
+ FirestoreCollection.TAG_ALL -> {
+ firestore.collection(FirestoreCollection.AllDoubts)
+ .orderBy("created_on", Query.Direction.DESCENDING)
+ }
+
+ FirestoreCollection.TAG_MY_COLLEGE -> {
+ firestore.collection(FirestoreCollection.AllDoubts)
+ .whereEqualTo("author_college", request.user.local_user_attr?.college)
+ .orderBy("created_on", Query.Direction.DESCENDING)
+ }
+
+ else -> {
+ firestore.collection(FirestoreCollection.AllDoubts)
+ .whereArrayContains("tags", request.tag)
+ .orderBy("created_on", Query.Direction.DESCENDING)
+ }
+ }
if (lastDoubtData != null && request.fetchFromPage1 == false) {
query = query.startAfter(lastDoubtData!!.date)
@@ -50,17 +68,18 @@ class FetchFeedByDateUseCase constructor(
}
@kotlin.jvm.Throws(Exception::class)
- private fun getDoubtDataList(result: QuerySnapshot?): List {
+ private suspend fun getDoubtDataList(result: QuerySnapshot?): List =
+ withContext(Dispatchers.IO) {
- val doubtDataList = mutableListOf()
+ val doubtDataList = mutableListOf()
- result!!.documents.forEach {
- val doubtData = DoubtData.parse(it) ?: return@forEach
- doubtDataList.add(doubtData)
- }
+ result!!.documents.forEach {
+ val doubtData = DoubtData.parse(it) ?: return@forEach
+ doubtDataList.add(doubtData)
+ }
- return doubtDataList
- }
+ return@withContext doubtDataList
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByPopularityUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByPopularityUseCase.kt
index cc2fbe6..a239c92 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByPopularityUseCase.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchFeedByPopularityUseCase.kt
@@ -2,7 +2,7 @@ package com.doubtless.doubtless.screens.home.usecases
import com.doubtless.doubtless.constants.FirestoreCollection
import com.doubtless.doubtless.screens.doubt.DoubtData
-import com.google.firebase.firestore.FieldPath
+import com.doubtless.doubtless.screens.doubt.usecases.FetchInteractedMentorDataForDoubt
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import com.google.firebase.firestore.QuerySnapshot
@@ -11,6 +11,7 @@ import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
class FetchFeedByPopularityUseCase constructor(
+ private val fetchInteractedMentorDataForDoubt: FetchInteractedMentorDataForDoubt,
private val firestore: FirebaseFirestore
) {
@@ -25,8 +26,25 @@ class FetchFeedByPopularityUseCase constructor(
withContext(Dispatchers.IO) {
try {
- var query = firestore.collection(FirestoreCollection.AllDoubts)
- .orderBy("net_votes", Query.Direction.DESCENDING)
+ var query = when (request.tag) {
+ FirestoreCollection.TAG_ALL -> {
+ firestore.collection(FirestoreCollection.AllDoubts)
+ .orderBy("net_votes", Query.Direction.DESCENDING)
+ }
+
+ FirestoreCollection.TAG_MY_COLLEGE -> {
+ firestore.collection(FirestoreCollection.AllDoubts)
+ .whereEqualTo("author_college", request.user.local_user_attr?.college)
+ .orderBy("net_votes", Query.Direction.DESCENDING)
+ }
+
+ else -> {
+ firestore.collection(FirestoreCollection.AllDoubts)
+ .whereArrayContains("tags", request.tag)
+ .orderBy("net_votes", Query.Direction.DESCENDING)
+ }
+ }
+
if (lastDoubtData != null && request.fetchFromPage1 == false) {
query = query.startAfter(lastDoubtData!!.date)
@@ -39,8 +57,7 @@ class FetchFeedByPopularityUseCase constructor(
val result = query.get().await()
val doubtDataList = getDoubtDataList(result)
- if (doubtDataList.isNotEmpty())
- lastDoubtData = doubtDataList.last()
+ if (doubtDataList.isNotEmpty()) lastDoubtData = doubtDataList.last()
return@withContext Result.Success(doubtDataList)
@@ -50,17 +67,18 @@ class FetchFeedByPopularityUseCase constructor(
}
@kotlin.jvm.Throws(Exception::class)
- private fun getDoubtDataList(result: QuerySnapshot?): List {
+ private suspend fun getDoubtDataList(result: QuerySnapshot?): List =
+ withContext(Dispatchers.IO) {
- val doubtDataList = mutableListOf()
+ val doubtDataList = mutableListOf()
- result!!.documents.forEach {
- val doubtData = DoubtData.parse(it) ?: return@forEach
- doubtDataList.add(doubtData)
- }
+ result!!.documents.forEach {
+ val doubtData = DoubtData.parse(it) ?: return@forEach
+ doubtDataList.add(doubtData)
+ }
- return doubtDataList
- }
+ return@withContext doubtDataList
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchHomeFeedUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchHomeFeedUseCase.kt
index f5905d0..6859705 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchHomeFeedUseCase.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/home/usecases/FetchHomeFeedUseCase.kt
@@ -1,16 +1,17 @@
package com.doubtless.doubtless.screens.home.usecases
+import android.os.UserManager
import android.util.Log
import androidx.annotation.WorkerThread
import com.doubtless.doubtless.constants.FirestoreCollection
import com.doubtless.doubtless.screens.auth.User
import com.doubtless.doubtless.screens.doubt.DoubtData
import com.doubtless.doubtless.screens.home.entities.FeedConfig
-import com.google.firebase.firestore.*
+import com.google.firebase.firestore.AggregateSource
+import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
-import java.lang.Math.abs
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -24,7 +25,10 @@ class FetchHomeFeedUseCase constructor(
data class FetchHomeFeedRequest(
val user: User,
val pageSize: Int = 10,
- val fetchFromPage1: Boolean = false
+ val fetchFromPage1: Boolean = false,
+ val tag: String
+
+
)
sealed class Result {
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotifContainerFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotifContainerFragment.kt
new file mode 100644
index 0000000..de036fa
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotifContainerFragment.kt
@@ -0,0 +1,59 @@
+package com.doubtless.doubtless.screens.inAppNotification
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.commit
+import com.doubtless.doubtless.DoubtlessApp
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.navigation.FragNavigator
+import com.doubtless.doubtless.navigation.OnBackPressListener
+import com.doubtless.doubtless.screens.dashboard.DashboardFragment
+import com.doubtless.doubtless.screens.main.MainActivity
+import com.doubtless.doubtless.screens.main.MainFragment
+
+class InAppNotificationContainerFragment: Fragment(R.layout.fragment_home) {
+
+ private lateinit var navigator: FragNavigator
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState == null) {
+ childFragmentManager.commit {
+ replace(R.id.bottomNav_child_container, InAppNotificationFragment())
+ }
+ }
+
+ navigator = DoubtlessApp.getInstance().getAppCompRoot()
+ .getInAppFragNavigator(requireActivity() as MainActivity)!!
+ }
+
+ private val onBackPressListener = object : OnBackPressListener {
+ override fun onBackPress(): Boolean {
+
+ val backPressConsumed = navigator.onBackPress()
+
+ return if (backPressConsumed)
+ true
+ else {
+ (parentFragment as? MainFragment)?.selectHomeBottomNavElement()
+ true
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ (requireActivity() as MainActivity).registerBackPress(onBackPressListener)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ (requireActivity() as MainActivity).unregisterBackPress(onBackPressListener)
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationAdapter.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationAdapter.kt
new file mode 100644
index 0000000..029308e
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationAdapter.kt
@@ -0,0 +1,50 @@
+package com.doubtless.doubtless.screens.inAppNotification
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+
+class InAppNotificationAdapter constructor(
+ private val notifications: List,
+ private val interactionListener: InteractionListener
+) : RecyclerView.Adapter() {
+
+ interface InteractionListener {
+ fun onPostAnswerNotifClicked(doubtId: String)
+ }
+
+ // rn there is only post answer type.
+ private val TYPE_POST_ANSWER = 1
+
+ private val data: MutableList = notifications.toMutableList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ // fyi: passing null in the parent, renders spacings incorrectly.
+ val itemView =
+ LayoutInflater.from(parent.context).inflate(R.layout.item_post_answer_notif, parent, false)
+ return PostAnswerNotificationViewHolder(itemView, interactionListener)
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ if (holder is PostAnswerNotificationViewHolder) {
+ holder.bind(data[position])
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return TYPE_POST_ANSWER
+ }
+
+ override fun getItemCount(): Int {
+ return data.size
+ }
+
+ fun setNewNotifications(newNotifications: MutableList) {
+ data.clear()
+ data.addAll(newNotifications)
+ notifyDataSetChanged()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationFragment.kt
new file mode 100644
index 0000000..09ad73e
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationFragment.kt
@@ -0,0 +1,148 @@
+package com.doubtless.doubtless.screens.inAppNotification
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.doubtless.doubtless.DoubtlessApp
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.databinding.FragmentInappNotifBinding
+import com.doubtless.doubtless.navigation.FragNavigator
+import com.doubtless.doubtless.navigation.OnBackPressListener
+import com.doubtless.doubtless.screens.doubt.usecases.FetchDoubtDataFromDoubtIdUseCase
+import com.doubtless.doubtless.screens.main.MainActivity
+import com.doubtless.doubtless.screens.main.MainFragment
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class InAppNotificationFragment : Fragment() {
+
+ private var _binding: FragmentInappNotifBinding? = null
+ private val binding get() = _binding!!
+
+ private lateinit var adapter: InAppNotificationAdapter
+ private lateinit var navigator: FragNavigator
+ private lateinit var fetchDoubtDataFromDoubtIdUseCase: FetchDoubtDataFromDoubtIdUseCase
+
+ private val viewModel: InAppNotificationViewModel by viewModels(
+ factoryProducer = {
+ InAppNotificationViewModel.Companion.Factory(
+ DoubtlessApp.getInstance().getAppCompRoot().getFetchNotificationUseCase(),
+ DoubtlessApp.getInstance().getAppCompRoot().getMarkInAppNotificationsReadUseCase()
+ )
+ }
+ )
+
+ private var onCreateEventUnConsumed = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ onCreateEventUnConsumed = true
+ navigator = DoubtlessApp.getInstance().getAppCompRoot()
+ .getInAppFragNavigator(requireActivity() as MainActivity)!!
+ fetchDoubtDataFromDoubtIdUseCase =
+ DoubtlessApp.getInstance().getAppCompRoot().getFetchDoubtDataFromDoubtIdUseCase()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentInappNotifBinding.inflate(inflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ if (onCreateEventUnConsumed) {
+ viewModel.fetchNotification()
+ onCreateEventUnConsumed = false
+ }
+
+ if (::adapter.isInitialized) {
+ binding.rvNotif.adapter = adapter
+ }
+
+ viewModel.notificationStatus.observe(viewLifecycleOwner) {
+ when (it) {
+ is InAppNotificationViewModel.Result.Success -> {
+
+ binding.progressBar.isVisible = false
+
+ if (::adapter.isInitialized == false) {
+
+ adapter = InAppNotificationAdapter(
+ it.notifications.toMutableList(),
+ object : InAppNotificationAdapter.InteractionListener {
+ override fun onPostAnswerNotifClicked(doubtId: String) {
+
+ CoroutineScope(Dispatchers.IO).launch {
+
+ val doubtData =
+ fetchDoubtDataFromDoubtIdUseCase.getDoubtData(doubtId)
+ ?: return@launch
+
+ if (!isAdded) return@launch
+
+ navigator.moveToDoubtDetailFragment(doubtData)
+ }
+ }
+ })
+
+ binding.rvNotif.adapter = adapter
+
+ if (it.additionalError != null)
+ Toast.makeText(requireContext(), it.additionalError, Toast.LENGTH_SHORT)
+ .show()
+ } else {
+ adapter.setNewNotifications(it.notifications.toMutableList())
+ }
+ }
+
+ is InAppNotificationViewModel.Result.Error -> {
+ binding.progressBar.isVisible = false
+ Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
+ }
+
+ is InAppNotificationViewModel.Result.NoData -> {
+
+ if (it.additionalError != null)
+ Toast.makeText(requireContext(), it.additionalError, Toast.LENGTH_SHORT)
+ .show()
+
+ binding.progressBar.isVisible = false
+ }
+
+ is InAppNotificationViewModel.Result.Loading -> {
+ binding.progressBar.isVisible = true
+ }
+ }
+ }
+ }
+
+ private val onBackPressListener = object : OnBackPressListener {
+ override fun onBackPress(): Boolean {
+ (parentFragment as? MainFragment)?.selectHomeBottomNavElement()
+ return true
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ (requireActivity() as MainActivity).registerBackPress(onBackPressListener)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ viewModel.markNotificationsAsRead()
+ (requireActivity() as MainActivity).unregisterBackPress(onBackPressListener)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationViewModel.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationViewModel.kt
new file mode 100644
index 0000000..d76add1
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/InAppNotificationViewModel.kt
@@ -0,0 +1,89 @@
+package com.doubtless.doubtless.screens.inAppNotification
+
+import androidx.lifecycle.*
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+import com.doubtless.doubtless.screens.inAppNotification.usecases.FetchInAppNotificationUseCase
+import com.doubtless.doubtless.screens.inAppNotification.usecases.FetchUnreadNotificationUseCase
+import com.doubtless.doubtless.screens.inAppNotification.usecases.MarkInAppNotificationsReadUseCase
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class InAppNotificationViewModel constructor(
+ private val fetchInAppNotificationUseCase: FetchInAppNotificationUseCase,
+ private val markInAppNotificationsReadUseCase: MarkInAppNotificationsReadUseCase
+) : ViewModel() {
+
+ sealed class Result {
+ class Success(
+ val notifications: List,
+ val additionalError: String? = null
+ ) : Result()
+
+ class NoData(val additionalError: String? = null) : Result()
+ class Error(val message: String) : Result()
+ object Loading : Result()
+ }
+
+ private val _notificationStatus = MutableLiveData()
+ val notificationStatus: LiveData = _notificationStatus
+
+ fun fetchNotification() = CoroutineScope(Dispatchers.IO).launch {
+
+ _notificationStatus.postValue(Result.Loading)
+
+ val result = fetchInAppNotificationUseCase.getNotifications()
+
+ when (result) {
+ is FetchInAppNotificationUseCase.Result.Success -> {
+ if (result.notifications.isNotEmpty())
+ _notificationStatus.postValue(
+ Result.Success(
+ notifications = result.notifications.distinctBy {
+ it.notificationId
+ }.sortedByDescending {
+ it.createdOn?.toDate()?.time
+ },
+ additionalError = result.error
+ )
+ )
+ else
+ _notificationStatus.postValue(Result.NoData(result.error))
+ }
+
+ is FetchInAppNotificationUseCase.Result.Error -> {
+ _notificationStatus.postValue(Result.Error(result.message))
+ }
+ }
+ }
+
+ fun markNotificationsAsRead() = CoroutineScope(Dispatchers.IO).launch {
+ if (_notificationStatus.value is Result.Success && _notificationStatus.value != null) {
+
+ val notifs =
+ (_notificationStatus.value!! as Result.Success).notifications.toMutableList()
+
+ markInAppNotificationsReadUseCase
+ .markRead(notifs)
+
+ _notificationStatus.postValue(Result.Success(notifs.map {
+ it.copy(isRead = true)
+ }))
+ }
+ }
+
+ companion object {
+ class Factory constructor(
+ private val fetchInAppNotificationUseCase: FetchInAppNotificationUseCase,
+ private val markInAppNotificationsReadUseCase: MarkInAppNotificationsReadUseCase
+ ) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ return InAppNotificationViewModel(
+ fetchInAppNotificationUseCase,
+ markInAppNotificationsReadUseCase
+ ) as T
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/PostAnswerNotificationViewHolder.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/PostAnswerNotificationViewHolder.kt
new file mode 100644
index 0000000..ea38f49
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/PostAnswerNotificationViewHolder.kt
@@ -0,0 +1,53 @@
+package com.doubtless.doubtless.screens.inAppNotification
+
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.bumptech.glide.Glide
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+import com.doubtless.doubtless.utils.Utils
+
+class PostAnswerNotificationViewHolder(
+ itemView: View,
+ private val interactionListener: InAppNotificationAdapter.InteractionListener
+) : ViewHolder(itemView) {
+
+ private val tvTopDescription: TextView = itemView.findViewById(R.id.tv_top_description)
+ private val tvDoubtDescription: TextView = itemView.findViewById(R.id.tv_doubt_description)
+ private val tvUsername: TextView = itemView.findViewById(R.id.tv_username)
+ private val tvUserYear: TextView = itemView.findViewById(R.id.user_year)
+ private val tvUserCollege: TextView = itemView.findViewById(R.id.user_college)
+ private val tvTimestamp: TextView = itemView.findViewById(R.id.author_answer_timestamp)
+ private val ivDp: ImageView = itemView.findViewById(R.id.iv_dp)
+ private val tvAnswerDescription: TextView = itemView.findViewById(R.id.user_answer_description)
+
+ fun bind(data: InAppNotificationEntity) {
+
+ itemView.setOnClickListener {
+ interactionListener.onPostAnswerNotifClicked(data.doubtId!!)
+ }
+
+ if (!data.isRead) {
+ itemView.setBackgroundColor(itemView.resources.getColor(R.color.light_purple))
+ } else {
+ itemView.setBackgroundColor(itemView.resources.getColor(R.color.white))
+ }
+
+ tvDoubtDescription.text = data.doubtHeading
+ Glide.with(itemView.context).load(data.authorPhotoUrl).circleCrop().into(ivDp)
+ tvUsername.text = data.answerAuthorName
+ tvUserYear.text = ""
+ tvUserCollege.text = ""
+
+ try {
+ tvTimestamp.text = Utils.getTimeAgo(data.createdOn?.toDate()!!)
+ } catch (e: Exception) {
+
+ }
+
+ tvAnswerDescription.text = data.description?.trim()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/dao/InAppNotificationDao.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/dao/InAppNotificationDao.kt
new file mode 100644
index 0000000..0c9e5c1
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/dao/InAppNotificationDao.kt
@@ -0,0 +1,17 @@
+package com.doubtless.doubtless.screens.inAppNotification.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+
+@Dao
+interface InAppNotificationDao {
+
+ @Query("SELECT * FROM InAppNotifications WHERE isRead = true")
+ fun getAllReadNotifications(): List
+
+ @Insert
+ fun insertNewNotifications(notificationEntities: List)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/model/InAppNotificationEntity.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/model/InAppNotificationEntity.kt
new file mode 100644
index 0000000..dd4e95b
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/model/InAppNotificationEntity.kt
@@ -0,0 +1,64 @@
+package com.doubtless.doubtless.screens.inAppNotification.model
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.google.errorprone.annotations.Keep
+import com.google.firebase.Timestamp
+import com.google.firebase.firestore.DocumentSnapshot
+import com.google.firebase.firestore.Exclude
+import com.google.firebase.firestore.IgnoreExtraProperties
+import com.google.firebase.firestore.ServerTimestamp
+import com.google.firebase.firestore.ktx.getField
+
+@Keep
+@Entity(tableName = "InAppNotifications")
+@IgnoreExtraProperties
+data class InAppNotificationEntity(
+ @get:Exclude
+ @PrimaryKey
+ val notificationId: String,
+ val answerAuthorId: String? = null,
+ val answerAuthorName: String? = null,
+ val answerId: String? = null,
+ val authorPhotoUrl: String? = null,
+ @ServerTimestamp
+ val createdOn: Timestamp? = null,
+ val description: String? = null,
+ val doubtAuthorId: String? = null,
+ val doubtHeading: String? = null,
+ val doubtId: String? = null,
+ val type: String? = TYPE_POST_ANSWER,
+ val isRead: Boolean = false
+) {
+ companion object {
+
+ const val TYPE_POST_ANSWER = "postAnswer"
+
+ fun fromDocumentSnapshot(documentSnapshot: DocumentSnapshot?): InAppNotificationEntity? {
+ return try {
+ val notif = InAppNotificationEntity(
+ notificationId = documentSnapshot!!.id,
+ answerAuthorId = documentSnapshot.getField("answer_author_id"),
+ answerAuthorName = documentSnapshot.getField("answer_author_name"),
+ answerId = documentSnapshot.getField("answer_id"),
+ authorPhotoUrl = documentSnapshot.getField("author_photo_url"),
+ createdOn = documentSnapshot.getField("created_on"),
+ description = documentSnapshot.getField("answer_description"),
+ doubtAuthorId = documentSnapshot.getField("doubt_author_id"),
+ doubtHeading = documentSnapshot.getField("doubt_heading"),
+ doubtId = documentSnapshot.getField("doubt_id"),
+ type = documentSnapshot.getField("type")
+ )
+
+ if (notif.type?.equals(TYPE_POST_ANSWER) == false) {
+ return null
+ }
+
+ return notif
+
+ } catch (e: Exception) {
+ null
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/FetchInAppNotificationUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/FetchInAppNotificationUseCase.kt
new file mode 100644
index 0000000..1a1dfd0
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/FetchInAppNotificationUseCase.kt
@@ -0,0 +1,52 @@
+package com.doubtless.doubtless.screens.inAppNotification.usecases
+
+import com.doubtless.doubtless.screens.inAppNotification.dao.InAppNotificationDao
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class FetchInAppNotificationUseCase constructor(
+ private val inAppNotificationDao: InAppNotificationDao,
+ private val unreadNotificationUseCase: FetchUnreadNotificationUseCase
+) {
+ sealed class Result {
+ class Success(val notifications: List, val error: String? = null) : Result()
+ class Error(val message: String): Result()
+ }
+
+ suspend fun getNotifications(): Result = withContext(Dispatchers.IO) {
+ try {
+ val notifications = mutableListOf()
+
+ var error: String? = null
+
+ // first fetch all unread notifications
+ val result = unreadNotificationUseCase.fetchNotification()
+
+ if (result is FetchUnreadNotificationUseCase.Result.Success) {
+ notifications.addAll(result.notifications)
+ }
+
+ if (result is FetchUnreadNotificationUseCase.Result.Error) {
+ error = result.message
+ }
+
+ // now fetch old read notifications from db
+ try {
+ notifications.addAll(inAppNotificationDao.getAllReadNotifications())
+ } catch (e: Exception) {
+ error = e.message
+ }
+
+ if (notifications.isEmpty() && error != null) {
+ return@withContext Result.Error(error)
+ }
+
+ return@withContext Result.Success(notifications, error)
+
+ } catch (e: Exception) {
+ return@withContext Result.Error(e.message ?: "some error occurred!")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/FetchUnreadNotificationUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/FetchUnreadNotificationUseCase.kt
new file mode 100644
index 0000000..7d6fd6b
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/FetchUnreadNotificationUseCase.kt
@@ -0,0 +1,48 @@
+package com.doubtless.doubtless.screens.inAppNotification.usecases
+
+import com.doubtless.doubtless.constants.FirestoreCollection
+import com.doubtless.doubtless.screens.auth.usecases.UserManager
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+import com.google.firebase.firestore.FirebaseFirestore
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
+
+class FetchUnreadNotificationUseCase constructor(
+ private val firestore: FirebaseFirestore,
+ private val userManager: UserManager
+) {
+
+ sealed class Result {
+ class Success(val notifications: List) : Result()
+ class Error(val message: String) : Result()
+ }
+
+ suspend fun fetchNotification(): Result = withContext(Dispatchers.IO) {
+ return@withContext try {
+
+ val userId = userManager.getCachedUserData()!!.id
+
+ val result = firestore.collection(FirestoreCollection.NOTIFICATION)
+ .whereEqualTo("doubt_author_id", userId)
+ .whereEqualTo("is_read", false)
+ .get().await()
+
+ val notifications = mutableListOf()
+
+ result.documents.forEach {
+ try {
+ notifications.add(InAppNotificationEntity.fromDocumentSnapshot(it)!!)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ return@withContext Result.Success(notifications)
+
+ } catch (e: Exception) {
+ Result.Error(e.message ?: "some error occurred!")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/MarkInAppNotificationsReadUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/MarkInAppNotificationsReadUseCase.kt
new file mode 100644
index 0000000..3ed8ba5
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/inAppNotification/usecases/MarkInAppNotificationsReadUseCase.kt
@@ -0,0 +1,52 @@
+package com.doubtless.doubtless.screens.inAppNotification.usecases
+
+import com.doubtless.doubtless.constants.FirestoreCollection
+import com.doubtless.doubtless.screens.inAppNotification.dao.InAppNotificationDao
+import com.doubtless.doubtless.screens.inAppNotification.model.InAppNotificationEntity
+import com.google.firebase.firestore.FirebaseFirestore
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class MarkInAppNotificationsReadUseCase constructor(
+ private val dao: InAppNotificationDao,
+ private val firestore: FirebaseFirestore
+) {
+
+ sealed class Result {
+ object Success : Result()
+ class Error(val message: String) : Result()
+ }
+
+ suspend fun markRead(notifications: List): Result =
+ withContext(Dispatchers.IO) {
+
+ try {
+ val markedReadNotifications = notifications.filter {
+ it.isRead == false
+ }.map {
+ it.copy(isRead = true)
+ }
+
+ markReadOnFirestore(notifications)
+
+ dao.insertNewNotifications(markedReadNotifications)
+
+ return@withContext Result.Success
+
+ } catch (e: Exception) {
+ return@withContext Result.Error(e.message ?: "some error occurred!")
+ }
+
+ }
+
+ private fun markReadOnFirestore(notifications: List) {
+ notifications.forEach {
+
+ firestore.collection(FirestoreCollection.NOTIFICATION)
+ .document(it.notificationId)
+ .update("is_read", true)
+
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/main/MainActivity.kt b/app/src/main/java/com/doubtless/doubtless/screens/main/MainActivity.kt
index 4827477..f1d70a8 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/main/MainActivity.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/main/MainActivity.kt
@@ -25,6 +25,10 @@ class MainActivity : AppCompatActivity(), BackPressDispatcher {
}
}
+ fun getMainFragment(): MainFragment? {
+ return supportFragmentManager.findFragmentByTag("MainFragment") as MainFragment?
+ }
+
// ----- backpress impl ------
private val backPressListeners: MutableList = mutableListOf()
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/main/MainFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/main/MainFragment.kt
index 336c7b5..eab986f 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/main/MainFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/main/MainFragment.kt
@@ -13,6 +13,8 @@ import com.doubtless.doubtless.screens.dashboard.DashboardFragment
import com.doubtless.doubtless.screens.doubt.create.CreateDoubtFragment
import com.doubtless.doubtless.screens.doubt.view.ViewDoubtsFragment
import com.doubtless.doubtless.screens.home.HomeFragment
+import com.doubtless.doubtless.screens.inAppNotification.InAppNotificationContainerFragment
+import com.doubtless.doubtless.screens.inAppNotification.InAppNotificationFragment
class MainFragment : Fragment() {
@@ -20,7 +22,12 @@ class MainFragment : Fragment() {
private val binding get() = _binding!!
private val bottomNavFragments =
- listOf(HomeFragment(), CreateDoubtFragment(), DashboardContainerFragment())
+ listOf(
+ HomeFragment(),
+ CreateDoubtFragment(),
+ InAppNotificationContainerFragment(),
+ DashboardContainerFragment()
+ )
private var areBottomNavFragmentsAdded = false
@@ -76,8 +83,33 @@ class MainFragment : Fragment() {
return binding.root
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ fun selectHomeBottomNavElement() {
+ if (!binding.btmNavHome.isChecked) {
+ binding.btmNavHome.callOnClick()
+ }
+ }
+
+ sealed class CurrentSelectedBottomNavFrag {
+ object HomeFrag : CurrentSelectedBottomNavFrag()
+ object CreateFrag : CurrentSelectedBottomNavFrag()
+ object InAppNotificationFrag : CurrentSelectedBottomNavFrag()
+ object DashboardFrag : CurrentSelectedBottomNavFrag()
+ object UnknownFrag : CurrentSelectedBottomNavFrag()
+ }
+
+ fun getCurrentSelectedElement(): CurrentSelectedBottomNavFrag {
+
+ val index = binding.retroBottomNav.getCurrentSelectedIndex()
+
+ return when (index) {
+ 0 -> CurrentSelectedBottomNavFrag.HomeFrag
+ 1 -> CurrentSelectedBottomNavFrag.CreateFrag
+ 2 -> CurrentSelectedBottomNavFrag.InAppNotificationFrag
+ 3 -> CurrentSelectedBottomNavFrag.DashboardFrag
+ else -> {
+ CurrentSelectedBottomNavFrag.UnknownFrag
+ }
+ }
}
override fun onDestroyView() {
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/BottomNavData.kt b/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/BottomNavData.kt
index 9297c5d..218b845 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/BottomNavData.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/BottomNavData.kt
@@ -14,4 +14,5 @@ interface OnSelectedItemChangedListener {
interface BottomIntractableElement {
fun onSelected()
fun onUnselected()
+ fun onReselected()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/RetroBottomNav.kt b/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/BottomNavLayout.kt
similarity index 89%
rename from app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/RetroBottomNav.kt
rename to app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/BottomNavLayout.kt
index 81404df..a052338 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/RetroBottomNav.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/main/bottomNav/BottomNavLayout.kt
@@ -1,20 +1,15 @@
package com.doubtless.doubtless.screens.main.bottomNav
import android.content.Context
-import android.os.Bundle
-import android.os.Parcelable
-import android.os.Vibrator
-import android.os.VibratorManager
import android.util.AttributeSet
import android.view.View
-import android.widget.LinearLayout
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.children
-import com.doubtless.doubtless.theming.buttons.SecondaryButton
-class RetroBottomNav(
+class BottomNavLayout(
context: Context,
attributeSet: AttributeSet
-) : LinearLayout(context, attributeSet) {
+) : ConstraintLayout(context, attributeSet) {
private var currentSelectedIndex: Int? = null
private val elements: ArrayList = arrayListOf()
@@ -44,11 +39,7 @@ class RetroBottomNav(
private fun onNewSelectedIndex(index: Int) {
onSelectedItemChangedListener?.onNewSelectedIndex(index)
-
elements[index].onSelected()
-//
-// (elements[index] as View).layoutParams =
-// LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
}
private fun notifyAllUnSelectedElements() {
@@ -66,7 +57,10 @@ class RetroBottomNav(
clickedBtn: BottomIntractableElement
) {
// proceed on only new index selection.
- if (currentSelectedIndex == idx) return
+ if (currentSelectedIndex == idx) {
+ clickedBtn.onReselected()
+ return
+ }
currentSelectedIndex = idx
onSelectedItemChangedListener?.onNewSelectedIndex(idx)
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/poll/CreatePollFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/poll/CreatePollFragment.kt
new file mode 100644
index 0000000..b4e1182
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/poll/CreatePollFragment.kt
@@ -0,0 +1,43 @@
+package com.doubtless.doubtless.screens.poll
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.databinding.FragmentCreatePollBinding
+
+
+class CreatePollFragment : Fragment() {
+
+ private var _binding : FragmentCreatePollBinding? = null
+ private val binding get() = _binding!!
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View? {
+ // Inflate the layout for this fragment
+ _binding = FragmentCreatePollBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.topbarPoll.setOnClickListener {
+ requireActivity().onBackPressed()
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ _binding=null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/poll/PollData.kt b/app/src/main/java/com/doubtless/doubtless/screens/poll/PollData.kt
new file mode 100644
index 0000000..936c186
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/poll/PollData.kt
@@ -0,0 +1,9 @@
+package com.doubtless.doubtless.screens.poll
+
+data class PollData(
+ val id: String? = null,
+ val pollOptions: List? = null,
+ val pollOptionVotes: List? = null,
+ val totalVotes: String? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/poll/PollFuncUseCase.kt b/app/src/main/java/com/doubtless/doubtless/screens/poll/PollFuncUseCase.kt
new file mode 100644
index 0000000..2b83789
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/poll/PollFuncUseCase.kt
@@ -0,0 +1,4 @@
+package com.doubtless.doubtless.screens.poll
+
+class PollFuncUseCase {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/poll/ViewPollViewHolder.kt b/app/src/main/java/com/doubtless/doubtless/screens/poll/ViewPollViewHolder.kt
new file mode 100644
index 0000000..4ac224a
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/screens/poll/ViewPollViewHolder.kt
@@ -0,0 +1,69 @@
+package com.doubtless.doubtless.screens.poll
+
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.doubtless.doubtless.R
+import com.doubtless.doubtless.screens.home.entities.FeedEntity
+
+class ViewPollViewHolder(val view: View, val interactionListener: InteractionListener):RecyclerView.ViewHolder(view) {
+
+ interface InteractionListener{
+ fun onPollOptionClicked(position : Int)
+ }
+
+ private val userName: TextView
+ private val heading: TextView
+ private val description: TextView
+ private val college: TextView
+ private val ivDp: ImageView
+ private val tvOption: TextView
+ private val progressBar: ProgressBar
+ private val llOptions: LinearLayout
+ private val time: TextView
+ private val tvYear: TextView
+
+ init {
+ userName = view.findViewById(R.id.tv_username_poll)
+ heading = view.findViewById(R.id.tv_poll_heading)
+ description = view.findViewById(R.id.tv_poll_description)
+ college = view.findViewById(R.id.tv_poll_college)
+ ivDp = view.findViewById(R.id.tv_user_dp_poll)
+ progressBar = view.findViewById(R.id.progress_poll)
+ tvOption = view.findViewById(R.id. tv_option)
+ llOptions = view.findViewById(R.id.ll_options)
+ time = view.findViewById(R.id.author_doubt_timestamp2)
+ tvYear = view.findViewById(R.id.user_year2)
+
+ }
+
+ fun setData(feedEntity: FeedEntity) {
+ llOptions.removeAllViews()
+
+ val pollOptions = feedEntity.pollOptions
+ if (pollOptions != null) {
+ for (i in pollOptions.indices) {
+ val option = pollOptions[i]
+ val optionView = createOptionView(option, i)
+ llOptions.addView(optionView)
+ }
+ }
+ }
+ private fun createOptionView(option: String, position: Int): View {
+ val optionView = LayoutInflater.from(view.context)
+ .inflate(R.layout.poll_options_layout, llOptions, false)
+ val tvOption = optionView.findViewById(R.id.tv_option)
+ tvOption.text = option
+
+ optionView.setOnClickListener {
+ interactionListener.onPollOptionClicked(position)
+ }
+
+ return optionView
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/screens/search/SearchFragment.kt b/app/src/main/java/com/doubtless/doubtless/screens/search/SearchFragment.kt
index 92c71fb..7f165c9 100644
--- a/app/src/main/java/com/doubtless/doubtless/screens/search/SearchFragment.kt
+++ b/app/src/main/java/com/doubtless/doubtless/screens/search/SearchFragment.kt
@@ -18,7 +18,12 @@ import com.doubtless.doubtless.screens.common.GenericFeedAdapter
import com.doubtless.doubtless.screens.doubt.DoubtData
import com.doubtless.doubtless.screens.main.MainActivity
import com.doubtless.doubtless.screens.search.usecases.FetchSearchResultsUseCase
-import kotlinx.coroutines.*
+import com.doubtless.doubtless.utils.hideSoftKeyboard
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
class SearchFragment : Fragment() {
@@ -53,24 +58,22 @@ class SearchFragment : Fragment() {
}
if (!::adapter.isInitialized) {
- adapter =
- GenericFeedAdapter(
- genericFeedEntities = mutableListOf(),
- onLastItemReached = {},
- interactionListener = object : GenericFeedAdapter.InteractionListener {
- override fun onSearchBarClicked() {}
-
- override fun onDoubtClicked(doubtData: DoubtData, position: Int) {
- analyticsTracker.trackSearchedDoubtClicked(doubtData.copy())
- navigator.moveToDoubtDetailFragment(doubtData)
- }
+ adapter = GenericFeedAdapter(genericFeedEntities = mutableListOf(),
+ onLastItemReached = {},
+ interactionListener = object : GenericFeedAdapter.InteractionListener {
+ override fun onDoubtClicked(doubtData: DoubtData, position: Int) {
+ analyticsTracker.trackSearchedDoubtClicked(doubtData.copy())
+ navigator.moveToDoubtDetailFragment(doubtData)
+ }
- override fun onSignOutClicked() {}
+ override fun onSignOutClicked() {}
- override fun onSubmitFeedbackClicked() {}
+ override fun onSubmitFeedbackClicked() {}
- override fun onDeleteAccountClicked() {}
- })
+ override fun onDeleteAccountClicked() {}
+ override fun onCreatePollClicked() {}
+ override fun onPollOptionClicked(position: Int) {}
+ })
}
binding.rvSearchResults.adapter = adapter
@@ -86,7 +89,7 @@ class SearchFragment : Fragment() {
binding.progressSearch.visibility = View.VISIBLE
- if (it.toString().length <= 1){
+ if (it.toString().length <= 1) {
delay(1000L)
binding.progressSearch.visibility = View.GONE
return@launch
@@ -108,11 +111,12 @@ class SearchFragment : Fragment() {
Toast.makeText(requireContext(), results.message, Toast.LENGTH_SHORT).show()
return@launch
}
- adapter.appendDoubts((results as FetchSearchResultsUseCase.Result.Success)
- .searchResult.map {
- it.toGenericEntity()
- })
+ adapter.appendDoubts((results as FetchSearchResultsUseCase.Result.Success).searchResult.map {
+ it.toGenericEntity()
+ })
binding.progressSearch.visibility = View.GONE
+ requireView().hideSoftKeyboard()
+ binding.etSearch.clearFocus()
}
}
@@ -129,10 +133,9 @@ class SearchFragment : Fragment() {
}
adapter.clearCurrentList()
- adapter.appendDoubts((results as FetchSearchResultsUseCase.Result.Success)
- .searchResult.map {
- it.toGenericEntity()
- })
+ adapter.appendDoubts((results as FetchSearchResultsUseCase.Result.Success).searchResult.map {
+ it.toGenericEntity()
+ })
}
}
diff --git a/app/src/main/java/com/doubtless/doubtless/theming/bottomNav/SoberBottomNavElementLayout.kt b/app/src/main/java/com/doubtless/doubtless/theming/bottomNav/SoberBottomNavElementLayout.kt
new file mode 100644
index 0000000..2ad3246
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/theming/bottomNav/SoberBottomNavElementLayout.kt
@@ -0,0 +1,24 @@
+package com.doubtless.doubtless.theming.bottomNav
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatCheckBox
+import com.doubtless.doubtless.screens.main.bottomNav.BottomIntractableElement
+
+class SoberBottomNavElementLayout(context: Context, attributeSet: AttributeSet?)
+ : AppCompatCheckBox(context, attributeSet), BottomIntractableElement {
+
+ override fun onSelected() {
+ isChecked = true
+ }
+
+ override fun onReselected() {
+ // on reselection the checked icon will toggle, hence manually make it checked.
+ isChecked = true
+ }
+
+ override fun onUnselected() {
+ isChecked = false
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/theming/buttons/RetroBottomNavElementLayout.kt b/app/src/main/java/com/doubtless/doubtless/theming/buttons/RetroBottomNavElementLayout.kt
index 600763a..db36ecd 100644
--- a/app/src/main/java/com/doubtless/doubtless/theming/buttons/RetroBottomNavElementLayout.kt
+++ b/app/src/main/java/com/doubtless/doubtless/theming/buttons/RetroBottomNavElementLayout.kt
@@ -37,4 +37,8 @@ class RetroBottomNavElementLayout(context: Context, attributeSet: AttributeSet?)
isCurrentlySelected = false
}
+ override fun onReselected() {
+
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/theming/buttons/SecondaryButton.kt b/app/src/main/java/com/doubtless/doubtless/theming/buttons/SecondaryButton.kt
index d017866..66db7d5 100644
--- a/app/src/main/java/com/doubtless/doubtless/theming/buttons/SecondaryButton.kt
+++ b/app/src/main/java/com/doubtless/doubtless/theming/buttons/SecondaryButton.kt
@@ -76,6 +76,10 @@ class SecondaryButton constructor(
setCardBackgroundColor(resources.getColor(R.color.cream))
}
+ override fun onReselected() {
+
+ }
+
override fun onUnselected() {
setCardBackgroundColor(resources.getColor(R.color.cream))
isCurrentlySelected = false
diff --git a/app/src/main/java/com/doubtless/doubtless/utils/Extensions.kt b/app/src/main/java/com/doubtless/doubtless/utils/Extensions.kt
new file mode 100644
index 0000000..1c9064a
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/utils/Extensions.kt
@@ -0,0 +1,19 @@
+package com.doubtless.doubtless.utils
+
+import android.animation.AnimatorInflater
+import android.content.Context
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import androidx.annotation.AnimatorRes
+
+fun View.addStateListAnimation(@AnimatorRes animation: Int) {
+ this.stateListAnimator = AnimatorInflater.loadStateListAnimator(
+ this.context,
+ animation
+ )
+}
+
+fun View.hideSoftKeyboard() {
+ val imm = this.context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
+ imm?.hideSoftInputFromWindow(this.windowToken, 0)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/utils/Resource.kt b/app/src/main/java/com/doubtless/doubtless/utils/Resource.kt
new file mode 100644
index 0000000..1907ac5
--- /dev/null
+++ b/app/src/main/java/com/doubtless/doubtless/utils/Resource.kt
@@ -0,0 +1,12 @@
+package com.doubtless.doubtless.utils
+
+sealed class Resource(
+ val data: T? = null,
+ val message: String? = null,
+ val error: Exception? = null
+) {
+ class Success(data: T) : Resource(data)
+ class Loading : Resource()
+ class Error(message: String? = null, data: T? = null, error: Exception? = null) :
+ Resource(data, message, error)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/doubtless/doubtless/utils/Utils.kt b/app/src/main/java/com/doubtless/doubtless/utils/Utils.kt
index c22cfc8..c7f6c9f 100644
--- a/app/src/main/java/com/doubtless/doubtless/utils/Utils.kt
+++ b/app/src/main/java/com/doubtless/doubtless/utils/Utils.kt
@@ -25,6 +25,12 @@ object Utils {
}
}
+ fun Int.toPx() = TypedValue.applyDimension(
+ /* unit = */ TypedValue.COMPLEX_UNIT_DIP,
+ /* value = */ this.toFloat(),
+ /* metrics = */ Resources.getSystem().displayMetrics
+ )
+
fun List.flatten(): String {
var string = ""
diff --git a/app/src/main/res/animator/scale_votes_icon.xml b/app/src/main/res/animator/scale_votes_icon.xml
new file mode 100644
index 0000000..1e2e200
--- /dev/null
+++ b/app/src/main/res/animator/scale_votes_icon.xml
@@ -0,0 +1,86 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_rec_rounded.xml b/app/src/main/res/drawable/bg_rec_rounded.xml
new file mode 100644
index 0000000..0e4826c
--- /dev/null
+++ b/app/src/main/res/drawable/bg_rec_rounded.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/create_checkable_icon.xml b/app/src/main/res/drawable/create_checkable_icon.xml
new file mode 100644
index 0000000..5d42350
--- /dev/null
+++ b/app/src/main/res/drawable/create_checkable_icon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/dashboard_checkable_icon.xml b/app/src/main/res/drawable/dashboard_checkable_icon.xml
new file mode 100644
index 0000000..5742478
--- /dev/null
+++ b/app/src/main/res/drawable/dashboard_checkable_icon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/home_checkable_icon.xml b/app/src/main/res/drawable/home_checkable_icon.xml
new file mode 100644
index 0000000..bebb2a2
--- /dev/null
+++ b/app/src/main/res/drawable/home_checkable_icon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_notifications_24.xml b/app/src/main/res/drawable/ic_baseline_notifications_24.xml
new file mode 100644
index 0000000..1d038a4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_notifications_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_add_24.xml b/app/src/main/res/drawable/ic_outline_add_24.xml
new file mode 100644
index 0000000..b063a36
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_add_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_comment_24.xml b/app/src/main/res/drawable/ic_outline_comment_24.xml
new file mode 100644
index 0000000..d0440be
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_comment_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_dashboard_24.xml b/app/src/main/res/drawable/ic_outline_dashboard_24.xml
new file mode 100644
index 0000000..5fbe0ef
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_dashboard_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_home_24.xml b/app/src/main/res/drawable/ic_outline_home_24.xml
new file mode 100644
index 0000000..678f2e0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_home_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_notifications_24.xml b/app/src/main/res/drawable/ic_outline_notifications_24.xml
new file mode 100644
index 0000000..3d20551
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_notifications_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_poll.xml b/app/src/main/res/drawable/ic_poll.xml
new file mode 100644
index 0000000..2411491
--- /dev/null
+++ b/app/src/main/res/drawable/ic_poll.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_reply_arrow_24.xml b/app/src/main/res/drawable/ic_reply_arrow_24.xml
new file mode 100644
index 0000000..f75e16a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_reply_arrow_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_verified.xml b/app/src/main/res/drawable/ic_verified.xml
new file mode 100644
index 0000000..84824b7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_verified.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/notif_checkable_icon.xml b/app/src/main/res/drawable/notif_checkable_icon.xml
new file mode 100644
index 0000000..b199ae9
--- /dev/null
+++ b/app/src/main/res/drawable/notif_checkable_icon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/poll_item_border.xml b/app/src/main/res/drawable/poll_item_border.xml
new file mode 100644
index 0000000..5f47c34
--- /dev/null
+++ b/app/src/main/res/drawable/poll_item_border.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/upvote_icon.xml b/app/src/main/res/drawable/upvote_icon.xml
new file mode 100644
index 0000000..8f17949
--- /dev/null
+++ b/app/src/main/res/drawable/upvote_icon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/user_profile_thumnail.png b/app/src/main/res/drawable/user_profile_thumnail.png
new file mode 100644
index 0000000..15ca8ee
Binary files /dev/null and b/app/src/main/res/drawable/user_profile_thumnail.png differ
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 3bbafb7..f555fd8 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -11,7 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30dp"
- app:cardCornerRadius="12dp"
+ app:cardCornerRadius="8dp"
app:cardElevation="12dp"
app:layout_constraintBottom_toBottomOf="@id/iv_banner"
app:layout_constraintTop_toTopOf="parent">
diff --git a/app/src/main/res/layout/answer_layout.xml b/app/src/main/res/layout/answer_layout.xml
index e7aaa65..8aa5de8 100644
--- a/app/src/main/res/layout/answer_layout.xml
+++ b/app/src/main/res/layout/answer_layout.xml
@@ -5,9 +5,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="0dp"
- android:layout_marginEnd="0dp"
- android:layout_marginBottom="0dp"
android:padding="10dp"
app:cardBackgroundColor="@color/cream"
app:cardCornerRadius="0dp"
@@ -16,17 +13,19 @@
+ android:layout_height="8dp"
+ android:background="@color/separation_grey"
+ android:visibility="gone" />
+
+
-
+ app:layout_constraintEnd_toStartOf="@id/tv_votes"
+ app:layout_constraintTop_toBottomOf="@id/author_answer_description_2" />
+ app:layout_constraintBottom_toBottomOf="@id/cb_upvote"
+ app:layout_constraintEnd_toStartOf="@id/cb_downvote"
+ app:layout_constraintTop_toTopOf="@id/cb_upvote" />
-
diff --git a/app/src/main/res/layout/doubt_layout.xml b/app/src/main/res/layout/doubt_layout.xml
index 4a0324e..9dd7409 100644
--- a/app/src/main/res/layout/doubt_layout.xml
+++ b/app/src/main/res/layout/doubt_layout.xml
@@ -16,7 +16,7 @@
+ android:background="@color/separation_grey" />
@@ -67,12 +67,12 @@
android:id="@+id/user_year"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="4dp"
+ android:layout_marginStart="2dp"
android:fontFamily="@font/poppins"
android:text="| 4th year |"
android:textColor="@color/grey"
android:textSize="12dp"
- app:layout_constraintStart_toEndOf="@id/tv_username"
+ app:layout_constraintStart_toEndOf="@+id/user_badge"
app:layout_constraintTop_toTopOf="@id/tv_username" />
+ app:layout_constraintTop_toBottomOf="@id/tv_poll_heading" />
+ app:layout_constraintTop_toBottomOf="@id/iv_votes"
+ tools:text="Related to : attendance, marks, teachers" />
+ app:layout_constraintTop_toBottomOf="@id/tv_poll_description" />
-
+ app:layout_constraintTop_toBottomOf="@id/tv_tags" />
-
+ app:layout_constraintStart_toEndOf="@id/cb_upvotes"
+ app:layout_constraintTop_toTopOf="@id/cb_upvotes" />
diff --git a/app/src/main/res/layout/enter_answer_layout.xml b/app/src/main/res/layout/enter_answer_layout.xml
index f6ddc70..31a50f2 100644
--- a/app/src/main/res/layout/enter_answer_layout.xml
+++ b/app/src/main/res/layout/enter_answer_layout.xml
@@ -16,8 +16,9 @@
+ android:layout_height="8dp"
+ android:visibility="gone"
+ android:background="@color/separation_grey" />
@@ -26,7 +27,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="22dp"
- android:paddingTop="15dp"
+ android:paddingTop="24dp"
android:paddingBottom="10dp">
+ android:src="@drawable/ic_baseline_arrow_back_ios_24"
+ app:tint="@color/white" />
+ android:textColor="@color/white"
+ android:textSize="16dp" />
diff --git a/app/src/main/res/layout/fragment_create_poll.xml b/app/src/main/res/layout/fragment_create_poll.xml
new file mode 100644
index 0000000..61da021
--- /dev/null
+++ b/app/src/main/res/layout/fragment_create_poll.xml
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home_main_screen.xml b/app/src/main/res/layout/fragment_home_main_screen.xml
new file mode 100644
index 0000000..9816de9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_home_main_screen.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_inapp_notif.xml b/app/src/main/res/layout/fragment_inapp_notif.xml
new file mode 100644
index 0000000..e43c4e5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_inapp_notif.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
index 75c382b..2dccd33 100644
--- a/app/src/main/res/layout/fragment_main.xml
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -14,127 +14,62 @@
app:layout_constraintBottom_toTopOf="@id/retro_bottom_nav"
app:layout_constraintTop_toTopOf="parent" />
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_post_answer_notif.xml b/app/src/main/res/layout/item_post_answer_notif.xml
new file mode 100644
index 0000000..254a777
--- /dev/null
+++ b/app/src/main/res/layout/item_post_answer_notif.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_home_buttons.xml b/app/src/main/res/layout/layout_home_buttons.xml
new file mode 100644
index 0000000..a83c8b1
--- /dev/null
+++ b/app/src/main/res/layout/layout_home_buttons.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_polls.xml b/app/src/main/res/layout/layout_polls.xml
new file mode 100644
index 0000000..ff98a34
--- /dev/null
+++ b/app/src/main/res/layout/layout_polls.xml
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/poll_create_layout.xml b/app/src/main/res/layout/poll_create_layout.xml
new file mode 100644
index 0000000..617f11f
--- /dev/null
+++ b/app/src/main/res/layout/poll_create_layout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/poll_options_layout.xml b/app/src/main/res/layout/poll_options_layout.xml
new file mode 100644
index 0000000..5caafa5
--- /dev/null
+++ b/app/src/main/res/layout/poll_options_layout.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/user_profile_layout.xml b/app/src/main/res/layout/user_profile_layout.xml
index a29f929..5395a10 100644
--- a/app/src/main/res/layout/user_profile_layout.xml
+++ b/app/src/main/res/layout/user_profile_layout.xml
@@ -2,35 +2,42 @@
+ android:elevation="0dp">
-
+ android:layout_height="wrap_content">
+
+
+ app:cardCornerRadius="50dp"
+ app:cardElevation="1dp"
+ app:layout_constraintBottom_toBottomOf="@id/iv_thumbnail"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/iv_thumbnail">
@@ -39,19 +46,40 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:layout_marginTop="4dp"
- android:text="User Name"
+ android:layout_marginTop="14dp"
+ android:fontFamily="@font/poppins"
android:textColor="@color/black"
- android:textSize="14dp" />
+ android:textSize="20dp"
+ app:layout_constraintStart_toStartOf="@id/cv_user_image"
+ app:layout_constraintTop_toBottomOf="@id/cv_user_image"
+ tools:text="Siddharth Sharma" />
+ android:layout_marginTop="0dp"
+ android:fontFamily="@font/poppins"
+ android:text="siddharthsharma@gmail.com"
+ android:textSize="14dp"
+ app:layout_constraintStart_toStartOf="@id/tv_name"
+ app:layout_constraintTop_toBottomOf="@id/tv_name" />
+
+
+ android:textSize="12dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ android:textSize="12dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_poll.xml b/app/src/main/res/layout/view_poll.xml
new file mode 100644
index 0000000..3498cf0
--- /dev/null
+++ b/app/src/main/res/layout/view_poll.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index d23b332..27bcd8e 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -8,5 +8,7 @@
#e090c9
#fab679
- #A93AFF
+ #7f5eff
+ #FAF4FF
+ #99E1E1E1
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d165fb3..181b759 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -19,4 +19,5 @@
Higher Studies
Placements
Please select at max 3 tags
+ Please sign in again!
\ No newline at end of file
diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml
new file mode 100644
index 0000000..c263808
--- /dev/null
+++ b/app/src/main/res/values/style.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index ef567ae..a10bca0 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -14,7 +14,7 @@
- @color/cream
- - true
+ - false
- true
- @color/cream