From 76d77d27cd5cdd4371279621f474ca04553c5faa Mon Sep 17 00:00:00 2001 From: alcantaraalvin Date: Sat, 20 Jul 2024 16:29:57 +0800 Subject: [PATCH 1/4] Initial commit for compose code on database --- database/app/build.gradle | 4 +- database/app/proguard-rules.pro | 2 +- database/app/src/main/AndroidManifest.xml | 8 +- .../database/EntryChoiceActivity.kt | 23 +- .../database/kotlin/BaseFragment.kt | 26 -- .../database/kotlin/MainActivity.kt | 34 +-- .../database/kotlin/MainFragment.kt | 85 ------ .../database/kotlin/NewPostFragment.kt | 132 --------- .../database/kotlin/PostDetailFragment.kt | 255 ------------------ .../database/kotlin/SignInFragment.kt | 155 ----------- .../kotlin/compose/AuthProviderViewModel.kt | 112 ++++++++ .../compose/DatabaseProviderViewModel.kt | 251 +++++++++++++++++ .../database/kotlin/compose/HomePage.kt | 176 ++++++++++++ .../database/kotlin/compose/LoginPage.kt | 208 ++++++++++++++ .../database/kotlin/compose/MainActivity.kt | 29 ++ .../database/kotlin/compose/MainPage.kt | 103 +++++++ .../database/kotlin/compose/MyPostsPage.kt | 37 +++ .../database/kotlin/compose/NewPostPage.kt | 82 ++++++ .../database/kotlin/compose/PostDetailPage.kt | 170 ++++++++++++ .../database/kotlin/compose/TopPostPage.kt | 44 +++ .../compose/flowcontrol/LoadDataStatus.kt | 9 + .../kotlin/compose/flowcontrol/LogInStatus.kt | 9 + .../compose/navigation/contents/Navigation.kt | 46 ++++ .../compose/navigation/contents/Screen.kt | 11 + .../kotlin/compose/topbar/TabLayout.kt | 106 ++++++++ .../kotlin/listfragments/MyPostsFragment.kt | 13 - .../listfragments/MyTopPostsFragment.kt | 15 -- .../kotlin/listfragments/PostListFragment.kt | 163 ----------- .../listfragments/RecentPostsFragment.kt | 16 -- .../quickstart/database/kotlin/models/Post.kt | 14 +- .../quickstart/database/kotlin/theme/Color.kt | 10 + .../quickstart/database/kotlin/theme/Shape.kt | 11 + .../quickstart/database/kotlin/theme/Theme.kt | 39 +++ .../quickstart/database/kotlin/theme/Type.kt | 18 ++ .../kotlin/viewholder/CommentViewHolder.kt | 14 - .../kotlin/viewholder/PostViewHolder.kt | 33 --- database/build.gradle | 3 + 37 files changed, 1517 insertions(+), 949 deletions(-) delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/BaseFragment.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainFragment.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/NewPostFragment.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/PostDetailFragment.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/SignInFragment.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainActivity.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MyPostsPage.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/TopPostPage.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LoadDataStatus.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LogInStatus.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Screen.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/topbar/TabLayout.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyPostsFragment.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyTopPostsFragment.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/PostListFragment.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/RecentPostsFragment.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Color.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Shape.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Theme.kt create mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Type.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/CommentViewHolder.kt delete mode 100644 database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/PostViewHolder.kt diff --git a/database/app/build.gradle b/database/app/build.gradle index 57305efb9..af62196ba 100644 --- a/database/app/build.gradle +++ b/database/app/build.gradle @@ -8,7 +8,7 @@ check.dependsOn 'assembleDebugAndroidTest' android { namespace 'com.google.firebase.quickstart.database' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "com.google.firebase.quickstart.database" @@ -89,6 +89,8 @@ dependencies { // Needed to fix a dependency conflict with FirebaseUI' implementation 'androidx.arch.core:core-runtime:2.1.0' + implementation 'androidx.compose.runtime:runtime-livedata:1.6.8' + implementation 'androidx.navigation:navigation-compose:2.7.7' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' diff --git a/database/app/proguard-rules.pro b/database/app/proguard-rules.pro index c0abaac4d..5ceba5ccc 100644 --- a/database/app/proguard-rules.pro +++ b/database/app/proguard-rules.pro @@ -29,6 +29,6 @@ *; } --keepclassmembers class com.google.firebase.quickstart.database.kotlin.models.** { +-keepclassmembers class com.google.firebase.quickstart.database.kotlin_old.models.** { *; } diff --git a/database/app/src/main/AndroidManifest.xml b/database/app/src/main/AndroidManifest.xml index 5d2c35f3c..7afd2e69e 100644 --- a/database/app/src/main/AndroidManifest.xml +++ b/database/app/src/main/AndroidManifest.xml @@ -6,7 +6,8 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:enableOnBackInvokedCallback="true"> @@ -26,6 +27,11 @@ android:name=".kotlin.MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar" /> + + diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/EntryChoiceActivity.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/EntryChoiceActivity.kt index da957ffaf..3e170bbe9 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/EntryChoiceActivity.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/EntryChoiceActivity.kt @@ -8,14 +8,21 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() { override fun getChoices(): List { return listOf( - Choice( - "Java", - "Run the Firebase Realtime Database quickstart written in Java.", - Intent(this, com.google.firebase.quickstart.database.java.MainActivity::class.java)), - Choice( - "Kotlin", - "Run the Firebase Realtime Database quickstart written in Kotlin.", - Intent(this, com.google.firebase.quickstart.database.kotlin.MainActivity::class.java)) + Choice( + "Java", + "Run the Firebase Realtime Database quickstart written in Java.", + Intent(this, com.google.firebase.quickstart.database.java.MainActivity::class.java) + ), + Choice( + "Kotlin", + "Run the Firebase Realtime Database quickstart written in Kotlin.", + Intent(this, com.google.firebase.quickstart.database.kotlin.MainActivity::class.java) + ), + Choice( + "Kotlin", + "Run the Firebase Realtime Database quickstart written in Compose.", + Intent(this, com.google.firebase.quickstart.database.kotlin.compose.MainActivity::class.java) + ) ) } } diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/BaseFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/BaseFragment.kt deleted file mode 100644 index c70be7781..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/BaseFragment.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin - -import android.view.View -import android.widget.ProgressBar -import androidx.fragment.app.Fragment -import com.google.firebase.auth.ktx.auth -import com.google.firebase.ktx.Firebase - -open class BaseFragment : Fragment() { - private var progressBar: ProgressBar? = null - - val uid: String - get() = Firebase.auth.currentUser!!.uid - - fun setProgressBar(resId: Int) { - progressBar = view?.findViewById(resId) - } - - fun showProgressBar() { - progressBar?.visibility = View.VISIBLE - } - - fun hideProgressBar() { - progressBar?.visibility = View.INVISIBLE - } -} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainActivity.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainActivity.kt index c8d206807..2ba730abc 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainActivity.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainActivity.kt @@ -1,36 +1,20 @@ package com.google.firebase.quickstart.database.kotlin import android.os.Bundle +import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.isGone -import androidx.core.view.isVisible -import androidx.navigation.findNavController -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.databinding.ActivityMainBinding +import androidx.compose.runtime.Composable +import com.google.firebase.quickstart.database.kotlin.compose.navigation.contents.Navigation class MainActivity : AppCompatActivity() { - - private lateinit var binding: ActivityMainBinding - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - val toolbar = binding.toolbar - setSupportActionBar(toolbar) - val fab = binding.fab - val navController = findNavController(R.id.nav_host_fragment) - navController.setGraph(R.navigation.nav_graph_kotlin) - navController.addOnDestinationChangedListener { _, destination, _ -> - if (destination.id == R.id.MainFragment) { - fab.isVisible = true - fab.setOnClickListener { - navController.navigate(R.id.action_MainFragment_to_NewPostFragment) - } - } else { - fab.isGone = true - } - } + setContent { Navigate() } } +} + +@Composable +fun Navigate() { + Navigation() } \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainFragment.kt deleted file mode 100644 index 560a66d69..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainFragment.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import androidx.core.view.MenuHost -import androidx.core.view.MenuProvider -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import androidx.viewpager2.adapter.FragmentStateAdapter -import com.google.android.material.tabs.TabLayoutMediator -import com.google.firebase.auth.ktx.auth -import com.google.firebase.ktx.Firebase -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.databinding.FragmentMainBinding -import com.google.firebase.quickstart.database.kotlin.listfragments.MyPostsFragment -import com.google.firebase.quickstart.database.kotlin.listfragments.MyTopPostsFragment -import com.google.firebase.quickstart.database.kotlin.listfragments.RecentPostsFragment - -class MainFragment : Fragment(), MenuProvider { - private var _binding: FragmentMainBinding? = null - private val binding get() = _binding!! - - private lateinit var pagerAdapter: FragmentStateAdapter - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - _binding = FragmentMainBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - // MenuProvider - val menuHost: MenuHost = requireActivity() as MenuHost - menuHost.addMenuProvider(this) - - // Create the adapter that will return a fragment for each section - pagerAdapter = object : FragmentStateAdapter(parentFragmentManager, viewLifecycleOwner.lifecycle) { - private val fragments = arrayOf( - RecentPostsFragment(), - MyPostsFragment(), - MyTopPostsFragment()) - - override fun createFragment(position: Int) = fragments[position] - - override fun getItemCount() = fragments.size - } - - // Set up the ViewPager with the sections adapter. - with(binding) { - container.adapter = pagerAdapter - TabLayoutMediator(tabs, container) { tab, position -> - tab.text = when(position) { - 0 -> getString(R.string.heading_recent) - 1 -> getString(R.string.heading_my_posts) - else -> getString(R.string.heading_my_top_posts) - } - }.attach() - } - } - - override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { - menuInflater.inflate(R.menu.menu_main, menu) - } - - override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - return if (menuItem.itemId == R.id.action_logout) { - Firebase.auth.signOut() - findNavController().navigate(R.id.action_MainFragment_to_SignInFragment) - true - } else { - false - } - } - - override fun onDestroy() { - super.onDestroy() - _binding = null - } -} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/NewPostFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/NewPostFragment.kt deleted file mode 100644 index b79067b3d..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/NewPostFragment.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin - -import android.os.Bundle -import android.text.TextUtils -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.navigation.fragment.findNavController -import com.google.firebase.database.DataSnapshot -import com.google.firebase.database.DatabaseError -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.ValueEventListener -import com.google.firebase.database.ktx.database -import com.google.firebase.database.ktx.getValue -import com.google.firebase.ktx.Firebase -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.databinding.FragmentNewPostBinding -import com.google.firebase.quickstart.database.kotlin.models.Post -import com.google.firebase.quickstart.database.kotlin.models.User - -class NewPostFragment : BaseFragment() { - private var _binding: FragmentNewPostBinding? = null - private val binding get() = _binding!! - - private lateinit var database: DatabaseReference - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - _binding = FragmentNewPostBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - database = Firebase.database.reference - - binding.fabSubmitPost.setOnClickListener { submitPost() } - } - - private fun submitPost() { - val title = binding.fieldTitle.text.toString() - val body = binding.fieldBody.text.toString() - - // Title is required - if (TextUtils.isEmpty(title)) { - binding.fieldTitle.error = REQUIRED - return - } - - // Body is required - if (TextUtils.isEmpty(body)) { - binding.fieldBody.error = REQUIRED - return - } - - // Disable button so there are no multi-posts - setEditingEnabled(false) - Toast.makeText(context, "Posting...", Toast.LENGTH_SHORT).show() - - val userId = uid - database.child("users").child(userId).addListenerForSingleValueEvent( - object : ValueEventListener { - override fun onDataChange(dataSnapshot: DataSnapshot) { - // Get user value - val user = dataSnapshot.getValue() - - if (user == null) { - // User is null, error out - Log.e(TAG, "User $userId is unexpectedly null") - Toast.makeText(context, - "Error: could not fetch user.", - Toast.LENGTH_SHORT).show() - } else { - // Write new post - writeNewPost(userId, user.username.toString(), title, body) - } - - setEditingEnabled(true) - findNavController().navigate(R.id.action_NewPostFragment_to_MainFragment) - } - - override fun onCancelled(databaseError: DatabaseError) { - Log.w(TAG, "getUser:onCancelled", databaseError.toException()) - setEditingEnabled(true) - } - }) - } - - private fun setEditingEnabled(enabled: Boolean) { - with(binding) { - fieldTitle.isEnabled = enabled - fieldBody.isEnabled = enabled - if (enabled) { - fabSubmitPost.show() - } else { - fabSubmitPost.hide() - } - } - } - - private fun writeNewPost(userId: String, username: String, title: String, body: String) { - // Create new post at /user-posts/$userid/$postid and at - // /posts/$postid simultaneously - val key = database.child("posts").push().key - if (key == null) { - Log.w(TAG, "Couldn't get push key for posts") - return - } - - val post = Post(userId, username, title, body) - val postValues = post.toMap() - - val childUpdates = hashMapOf( - "/posts/$key" to postValues, - "/user-posts/$userId/$key" to postValues - ) - - database.updateChildren(childUpdates) - } - - override fun onDestroy() { - super.onDestroy() - _binding = null - } - - companion object { - private const val TAG = "NewPostFragment" - private const val REQUIRED = "Required" - } -} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/PostDetailFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/PostDetailFragment.kt deleted file mode 100644 index 546e47db1..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/PostDetailFragment.kt +++ /dev/null @@ -1,255 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin - -import android.content.Context -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.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.google.firebase.database.ChildEventListener -import com.google.firebase.database.DatabaseError -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.DataSnapshot -import com.google.firebase.database.ValueEventListener -import com.google.firebase.database.ktx.database -import com.google.firebase.database.ktx.getValue -import com.google.firebase.ktx.Firebase -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.databinding.FragmentPostDetailBinding -import com.google.firebase.quickstart.database.kotlin.models.Comment -import com.google.firebase.quickstart.database.kotlin.models.Post -import com.google.firebase.quickstart.database.kotlin.models.User -import com.google.firebase.quickstart.database.kotlin.viewholder.CommentViewHolder -import java.lang.IllegalArgumentException -import java.util.ArrayList - -class PostDetailFragment : BaseFragment() { - - private lateinit var postKey: String - private lateinit var postReference: DatabaseReference - private lateinit var commentsReference: DatabaseReference - - private var postListener: ValueEventListener? = null - private var adapter: CommentAdapter? = null - - private var _binding: FragmentPostDetailBinding? = null - private val binding get() = _binding!! - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - _binding = FragmentPostDetailBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - // Get post key from arguments - postKey = requireArguments().getString(EXTRA_POST_KEY) - ?: throw IllegalArgumentException("Must pass EXTRA_POST_KEY") - - // Initialize Database - postReference = Firebase.database.reference - .child("posts").child(postKey) - commentsReference = Firebase.database.reference - .child("post-comments").child(postKey) - - // Initialize Views - with(binding) { - buttonPostComment.setOnClickListener { postComment() } - recyclerPostComments.layoutManager = LinearLayoutManager(context) - } - } - - override fun onStart() { - super.onStart() - - // Add value event listener to the post - val postListener = object : ValueEventListener { - override fun onDataChange(dataSnapshot: DataSnapshot) { - // Get Post object and use the values to update the UI - val post = dataSnapshot.getValue() - post?.let { - binding.postAuthorLayout.postAuthor.text = it.author - with(binding.postTextLayout) { - postTitle.text = it.title - postBody.text = it.body - } - } - } - - override fun onCancelled(databaseError: DatabaseError) { - // Getting Post failed, log a message - Log.w(TAG, "loadPost:onCancelled", databaseError.toException()) - Toast.makeText(context, "Failed to load post.", - Toast.LENGTH_SHORT).show() - } - } - postReference.addValueEventListener(postListener) - - // Keep copy of post listener so we can remove it when app stops - this.postListener = postListener - - // Listen for comments - adapter = CommentAdapter(requireContext(), commentsReference) - binding.recyclerPostComments.adapter = adapter - } - - override fun onStop() { - super.onStop() - - // Remove post value event listener - postListener?.let { - postReference.removeEventListener(it) - } - - // Clean up comments listener - adapter?.cleanupListener() - } - - private fun postComment() { - val uid = uid - Firebase.database.reference.child("users").child(uid) - .addListenerForSingleValueEvent(object : ValueEventListener { - override fun onDataChange(dataSnapshot: DataSnapshot) { - // Get user information - val user = dataSnapshot.getValue() ?: return - - val authorName = user.username - - // Create new comment object - val commentText = binding.fieldCommentText.text.toString() - val comment = Comment(uid, authorName, commentText) - - // Push the comment, it will appear in the list - commentsReference.push().setValue(comment) - - // Clear the field - binding.fieldCommentText.text = null - } - - override fun onCancelled(databaseError: DatabaseError) { - } - }) - } - - private class CommentAdapter( - private val context: Context, - private val databaseReference: DatabaseReference - ) : RecyclerView.Adapter() { - - private val childEventListener: ChildEventListener? - - private val commentIds = ArrayList() - private val comments = ArrayList() - - init { - - // Create child event listener - val childEventListener = object : ChildEventListener { - override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) { - Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!) - - // A new comment has been added, add it to the displayed list - val comment = dataSnapshot.getValue() - - // Update RecyclerView - commentIds.add(dataSnapshot.key!!) - comments.add(comment!!) - notifyItemInserted(comments.size - 1) - } - - override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) { - Log.d(TAG, "onChildChanged: ${dataSnapshot.key}") - - // A comment has changed, use the key to determine if we are displaying this - // comment and if so displayed the changed comment. - val newComment = dataSnapshot.getValue() - val commentKey = dataSnapshot.key - - val commentIndex = commentIds.indexOf(commentKey) - if (commentIndex > -1 && newComment != null) { - // Replace with the new data - comments[commentIndex] = newComment - - // Update the RecyclerView - notifyItemChanged(commentIndex) - } else { - Log.w(TAG, "onChildChanged:unknown_child: $commentKey") - } - } - - override fun onChildRemoved(dataSnapshot: DataSnapshot) { - Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!) - - // A comment has changed, use the key to determine if we are displaying this - // comment and if so remove it. - val commentKey = dataSnapshot.key - - val commentIndex = commentIds.indexOf(commentKey) - if (commentIndex > -1) { - // Remove data from the list - commentIds.removeAt(commentIndex) - comments.removeAt(commentIndex) - - // Update the RecyclerView - notifyItemRemoved(commentIndex) - } else { - Log.w(TAG, "onChildRemoved:unknown_child:" + commentKey!!) - } - } - - override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) { - Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!) - - // A comment has changed position, use the key to determine if we are - // displaying this comment and if so move it. - val movedComment = dataSnapshot.getValue() - val commentKey = dataSnapshot.key - - // ... - } - - override fun onCancelled(databaseError: DatabaseError) { - Log.w(TAG, "postComments:onCancelled", databaseError.toException()) - Toast.makeText(context, "Failed to load comments.", - Toast.LENGTH_SHORT).show() - } - } - databaseReference.addChildEventListener(childEventListener) - - // Store reference to listener so it can be removed on app stop - this.childEventListener = childEventListener - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder { - val inflater = LayoutInflater.from(context) - val view = inflater.inflate(R.layout.item_comment, parent, false) - return CommentViewHolder(view) - } - - override fun onBindViewHolder(holder: CommentViewHolder, position: Int) { - holder.bind(comments[position]) - } - - override fun getItemCount(): Int = comments.size - - fun cleanupListener() { - childEventListener?.let { - databaseReference.removeEventListener(it) - } - } - } - - override fun onDestroy() { - super.onDestroy() - _binding = null - } - - companion object { - private const val TAG = "PostDetailFragment" - const val EXTRA_POST_KEY = "post_key" - } -} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/SignInFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/SignInFragment.kt deleted file mode 100644 index 0c198bdb2..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/SignInFragment.kt +++ /dev/null @@ -1,155 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin - -import android.os.Bundle -import android.text.TextUtils -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.navigation.fragment.findNavController -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.FirebaseUser -import com.google.firebase.auth.ktx.auth -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.ktx.database -import com.google.firebase.ktx.Firebase -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.databinding.FragmentSignInBinding -import com.google.firebase.quickstart.database.kotlin.models.User - -class SignInFragment : BaseFragment() { - private var _binding: FragmentSignInBinding? = null - private val binding get() = _binding!! - - private lateinit var database: DatabaseReference - private lateinit var auth: FirebaseAuth - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - _binding = FragmentSignInBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - database = Firebase.database.reference - auth = Firebase.auth - - setProgressBar(R.id.progressBar) - - // Click listeners - with(binding) { - buttonSignIn.setOnClickListener { signIn() } - buttonSignUp.setOnClickListener { signUp() } - } - } - - override fun onStart() { - super.onStart() - - // Check auth on Fragment start - auth.currentUser?.let { - onAuthSuccess(it) - } - } - - private fun signIn() { - Log.d(TAG, "signIn") - if (!validateForm()) { - return - } - - showProgressBar() - val email = binding.fieldEmail.text.toString() - val password = binding.fieldPassword.text.toString() - - auth.signInWithEmailAndPassword(email, password) - .addOnCompleteListener(requireActivity()) { task -> - Log.d(TAG, "signIn:onComplete:" + task.isSuccessful) - hideProgressBar() - - if (task.isSuccessful) { - onAuthSuccess(task.result?.user!!) - } else { - Toast.makeText(context, "Sign In Failed", - Toast.LENGTH_SHORT).show() - } - } - } - - private fun signUp() { - Log.d(TAG, "signUp") - if (!validateForm()) { - return - } - - showProgressBar() - val email = binding.fieldEmail.text.toString() - val password = binding.fieldPassword.text.toString() - - auth.createUserWithEmailAndPassword(email, password) - .addOnCompleteListener(requireActivity()) { task -> - Log.d(TAG, "createUser:onComplete:" + task.isSuccessful) - hideProgressBar() - - if (task.isSuccessful) { - onAuthSuccess(task.result?.user!!) - } else { - Toast.makeText(context, "Sign Up Failed", - Toast.LENGTH_SHORT).show() - } - } - } - - private fun onAuthSuccess(user: FirebaseUser) { - val username = usernameFromEmail(user.email!!) - - // Write new user - writeNewUser(user.uid, username, user.email) - - // Go to MainFragment - findNavController().navigate(R.id.action_SignInFragment_to_MainFragment) - } - - private fun usernameFromEmail(email: String): String { - return if (email.contains("@")) { - email.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] - } else { - email - } - } - - private fun validateForm(): Boolean { - var result = true - if (TextUtils.isEmpty(binding.fieldEmail.text.toString())) { - binding.fieldEmail.error = "Required" - result = false - } else { - binding.fieldEmail.error = null - } - - if (TextUtils.isEmpty(binding.fieldPassword.text.toString())) { - binding.fieldPassword.error = "Required" - result = false - } else { - binding.fieldPassword.error = null - } - - return result - } - - private fun writeNewUser(userId: String, name: String, email: String?) { - val user = User(name, email) - database.child("users").child(userId).setValue(user) - } - - override fun onDestroy() { - super.onDestroy() - _binding = null - } - - companion object { - private const val TAG = "SignInFragment" - } -} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt new file mode 100644 index 000000000..d59706101 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt @@ -0,0 +1,112 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseUser +import com.google.firebase.auth.ktx.auth +import com.google.firebase.database.FirebaseDatabase +import com.google.firebase.ktx.Firebase +import com.google.firebase.quickstart.database.kotlin.compose.flowcontrol.LogInStatus +import com.google.firebase.quickstart.database.kotlin.models.User +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await + +class AuthProviderViewModel( + val auth: FirebaseAuth +) : ViewModel() { + + private val _signUpFlow = MutableStateFlow?>(null) + private val signInFlow = MutableStateFlow?>(null) + val loginFlow: StateFlow?> = signInFlow + val signUpFlow: StateFlow?> = _signUpFlow + + companion object { + + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, + extras: CreationExtras + ): T { + // Get instance. + val auth = Firebase.auth + return AuthProviderViewModel(auth) as T + } + } + } + + fun validateForm(email: String, password: String): Boolean { + var result = true + if (email.isEmpty()) { + result = false + } else { + } + + if (password.isEmpty()) { + result = false + } else { + } + + return result + } + + + fun signIn(email: String, password: String, database: FirebaseDatabase) = viewModelScope.launch { + + signInFlow.value = LogInStatus.InitState + + if (!validateForm(email, password)) { + return@launch + } + + try { + auth.signInWithEmailAndPassword(email, password).await() + onAuthSuccess(auth.currentUser!!, database) + signInFlow.value = LogInStatus.Success() + } catch (e: Exception) { + signInFlow.value = LogInStatus.Failure(e) + } + } + + + fun signUp(email: String, password: String, database: FirebaseDatabase) = viewModelScope.launch { + + if (!validateForm(email, password)) { + _signUpFlow.value = LogInStatus.Failure(Exception()) + return@launch + } + + try { + auth.createUserWithEmailAndPassword(email, password).await() + onAuthSuccess(auth.currentUser!!, database) + _signUpFlow.value = LogInStatus.Success() + } catch (e: Exception) { + _signUpFlow.value = LogInStatus.Failure(e) + } + } + + fun onAuthSuccess(user: FirebaseUser, database: FirebaseDatabase) { + val username = usernameFromEmail(user.email!!) + + writeNewUser(user.uid, username, user.email, database) + } + + fun usernameFromEmail(email: String): String { + return if (email.contains("@")) { + email.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + } else { + email + } + } + + fun writeNewUser(userId: String, name: String, email: String?, database: FirebaseDatabase) { + val user = User(name, email) + // database.child("users").child(userId).setValue(user) + database.reference.child("users").child(userId).setValue(user) + } +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt new file mode 100644 index 000000000..a950f0594 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt @@ -0,0 +1,251 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import android.util.Log +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.google.firebase.database.DataSnapshot +import com.google.firebase.database.DatabaseError +import com.google.firebase.database.DatabaseReference +import com.google.firebase.database.FirebaseDatabase +import com.google.firebase.database.MutableData +import com.google.firebase.database.Transaction +import com.google.firebase.database.ValueEventListener +import com.google.firebase.database.ktx.database +import com.google.firebase.database.ktx.getValue +import com.google.firebase.ktx.Firebase +import com.google.firebase.quickstart.database.kotlin.compose.flowcontrol.LoadDataStatus +import com.google.firebase.quickstart.database.kotlin.models.Comment +import com.google.firebase.quickstart.database.kotlin.models.Post +import com.google.firebase.quickstart.database.kotlin.models.User +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await + + +class DatabaseProviderViewModel( + val database: FirebaseDatabase +) : ViewModel() { + + //variables for posts + private val postList = mutableStateListOf() + private val postDetails = mutableStateOf(Post()) + private val commentList = mutableStateListOf() + + //variables for flow of loading data + private val _dataLoadFlow = MutableStateFlow?>(null) + val dataLoadFlow: StateFlow?> = _dataLoadFlow + + //for handling new post + private val _title = mutableStateOf("") + private val _body = mutableStateOf("") + private val _uid = mutableStateOf("") + + companion object { + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, extras: CreationExtras + ): T { + // Get instance. + val database = Firebase.database + return DatabaseProviderViewModel(database) as T + } + } + } + + fun setContent(title: String, body: String, uid: String) { + _title.value = title + _body.value = body + _uid.value = uid + } + + private fun writeNewPost(userId: String, username: String, title: String, body: String) = viewModelScope.launch { + // Create new post at /user-posts/$userid/$postid and at + // /posts/$postid simultaneously + val key = database.reference.child("posts").push().key ?: return@launch + val post = Post(key, userId, username, title, body) + val postValues = post.toMap() + + val childUpdates = hashMapOf( + "/posts/$key" to postValues, "/user-posts/$userId/$key" to postValues + ) + + _dataLoadFlow.value = LoadDataStatus.Loading + try { + database.reference.updateChildren(childUpdates).await() + _dataLoadFlow.value = LoadDataStatus.Loaded() + } catch (e: Exception) { + _dataLoadFlow.value = LoadDataStatus.Failed(e) + } + } + + fun submitPost() = viewModelScope.launch { + + // Title is required + if (_title.value.isEmpty()) { + return@launch + } + + if (_body.value.isEmpty()) { + return@launch + } + + val userId = _uid.value + database.reference.child("users").child(userId).addListenerForSingleValueEvent(object : ValueEventListener { + override fun onDataChange(dataSnapshot: DataSnapshot) { + // Get user value + val user = dataSnapshot.getValue() + if (user == null) { + // User is null, error out + Log.e("NewPostFragment", "User $userId is unexpectedly null")/* Toast.makeText(context, + "Error: could not fetch user.", + Toast.LENGTH_SHORT).show()*/ + } else { + // Write new post + writeNewPost(userId, user.username.toString(), _title.value, _body.value) + } + } + + override fun onCancelled(databaseError: DatabaseError) { + Log.w("NewPostFragment", "getUser:onCancelled", databaseError.toException()) + } + }) + } + + //for post detail and comments page + fun getPostDetail(uid: String): Post { + setPostDetail(uid) + return postDetails.value + } + + fun setPostValues(post: Post) { + postDetails.value = post + } + + fun getCommentLists(): MutableList { + return commentList + } + + fun initCommentLists(commentId: String) { + + val commentsReference = Firebase.database.reference + .child("post-comments").child(commentId) + + commentsReference.addValueEventListener(object : ValueEventListener { + override fun onDataChange(snapshot: DataSnapshot) { + commentList.clear() + for (s in snapshot.children) { + var comment = s.getValue(Comment::class.java) + commentList.add(comment!!) + } + + } + + override fun onCancelled(p0: DatabaseError) { + } + } + ) + } + + fun postComment(commentId: String, uid: String, commentText: String) { + val commentsReference = Firebase.database.reference + .child("post-comments").child(commentId) + + database.reference.child("users").child(uid) + .addListenerForSingleValueEvent(object : ValueEventListener { + override fun onDataChange(snapshot: DataSnapshot) { + val user = snapshot.getValue() ?: return + + val authorName = user.username + val comment = Comment(uid, authorName, commentText) + commentsReference.push().setValue(comment) + initCommentLists(commentId) + } + + override fun onCancelled(p0: DatabaseError) { + } + } + ) + } + + private fun setPostDetail(uid: String) { + database.getReference("posts").addValueEventListener(object : ValueEventListener { + override fun onDataChange(snapshot: DataSnapshot) { + val post = snapshot.child(uid).getValue(Post::class.java) + post?.key = snapshot.child(uid).key.toString() + setPostValues(post!!) + } + + override fun onCancelled(error: DatabaseError) { + } + + }) + } + + fun setStarStatus(postRef: DatabaseReference, uid: String) { + postRef.runTransaction(object : Transaction.Handler { + override fun doTransaction(mutableData: MutableData): Transaction.Result { + val p = mutableData.getValue(Post::class.java) + ?: return Transaction.success(mutableData) + if (p.stars.containsKey(uid)) { + // Unstar the post and remove self from stars + p.starCount = p.starCount - 1 + p.stars.remove(uid) + } else { + // Star the post and add self to stars + p.starCount = p.starCount + 1 + p.stars[uid] = true + } + + // Set value and report transaction success + mutableData.value = p + return Transaction.success(mutableData) + } + + override fun onComplete( + databaseError: DatabaseError?, + committed: Boolean, + currentData: DataSnapshot? + ) { + //update the list + database.reference.child("posts") + } + }) + } + + //get the list of posts + fun getPosts(databaseReference: DatabaseReference): MutableList { + updateList(databaseReference) + return postList + } + + //update list + private fun updateList(databaseReference: DatabaseReference) { + _dataLoadFlow.value = LoadDataStatus.Loading + databaseReference.addValueEventListener(object : ValueEventListener { + override fun onDataChange(snapshot: DataSnapshot) { + postList.clear() + for (s in snapshot.children) { + var post = s.getValue(Post::class.java) + post?.key = s.key.toString() + postList.add(post!!) + } + _dataLoadFlow.value = LoadDataStatus.Loaded() + } + + override fun onCancelled(error: DatabaseError) { + _dataLoadFlow.value = LoadDataStatus.Failed(error.toException()) + } + + }) + } + + +} + + diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt new file mode 100644 index 000000000..a9f90d0e1 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt @@ -0,0 +1,176 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import android.annotation.SuppressLint +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Card +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.core.os.bundleOf +import androidx.navigation.NavHostController +import com.google.firebase.database.DatabaseReference +import com.google.firebase.quickstart.database.R +import com.google.firebase.quickstart.database.kotlin.compose.navigation.contents.Screen +import com.google.firebase.quickstart.database.kotlin.models.Post + + +@Composable +fun HomePage( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel +) { + + Scaffold( + content = { + Column( + modifier = Modifier.padding(it) + ) { + Home(rootNavController, databaseProviderViewModel, authProviderViewModel) + } + }, + ) +} + + +@Composable +fun Home( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel, +) { + + val uid = authProviderViewModel.auth.uid!! + val databaseref = databaseProviderViewModel.database.reference.child("posts") + val posts = databaseProviderViewModel.getPosts(databaseref) + posts.reverse() + + if (posts != null) { + displayList(databaseProviderViewModel, uid, rootNavController, databaseref) + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun displayList( + databaseProviderViewModel: DatabaseProviderViewModel, + uid: String, + rootNavController: NavHostController, + databaseReference: DatabaseReference +) { + val starResourceNotLiked = R.drawable.ic_toggle_star_outline_24 + val starResourceLiked = R.drawable.ic_toggle_star_24 + var starDisplay by remember { mutableStateOf(starResourceLiked) } + var isLike: Boolean + + val posts = databaseProviderViewModel.getPosts(databaseReference) + + Spacer(modifier = Modifier.height(5.dp)) + LazyColumn { + items(posts) { post -> + Card( + shape = RectangleShape, + elevation = 4.dp, + modifier = Modifier + .padding(1.dp, 2.dp, 5.dp, 0.dp) + .fillMaxWidth(), + onClick = { + onCardClick(rootNavController, post.key!!) + } + ) { + Column( + Modifier + .padding(16.dp, 0.dp, 0.dp, 0.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painterResource(R.drawable.ic_action_account_circle_40), + contentDescription = "Firebase user", + contentScale = ContentScale.Crop, + ) + Spacer(modifier = Modifier.width(10.dp)) + Text(text = post.author.toString()) + + isLike = post.stars.containsKey(uid!!) + starDisplay = if (isLike) { + starResourceLiked + } else { + starResourceNotLiked + } + + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier + .fillMaxWidth() + .padding(0.dp, 0.dp, 10.dp, 0.dp) + ) { + Image( + painterResource(starDisplay), + contentDescription = "star", + contentScale = ContentScale.Crop, + alignment = Alignment.CenterEnd, + modifier = Modifier.clickable { + onStarClick(databaseProviderViewModel, uid, post) + } + ) + Text(text = post.starCount.toString()) + } + + } + Spacer(modifier = Modifier.height(20.dp)) + Row { + Text(text = post.title.toString()) + } + Spacer(modifier = Modifier.height(15.dp)) + Row { + Text(text = post.body.toString()) + } + } + } + } + } +} + +fun onStarClick(databaseProviderViewModel: DatabaseProviderViewModel, uid: String, post: Post) { + + val globalPostRef = databaseProviderViewModel.database.reference.child("posts").child(post.key!!) + val userPostRef = + databaseProviderViewModel.database.reference.child("user-posts").child(post.uid!!).child(post.key!!) + + databaseProviderViewModel.setStarStatus(globalPostRef, uid) + databaseProviderViewModel.setStarStatus(userPostRef, uid) +} + +@SuppressLint("RestrictedApi") +fun onCardClick(rootNavController: NavHostController, uid: String) { + + val bundle = bundleOf("uid" to uid) + val destination = rootNavController.findDestination(Screen.CommentScreen.route) + if (destination != null) { + rootNavController.navigate(destination.id, bundle) + } +} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt new file mode 100644 index 000000000..68312ff75 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt @@ -0,0 +1,208 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import android.annotation.SuppressLint +import android.widget.Toast +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.google.firebase.quickstart.database.R +import com.google.firebase.quickstart.database.kotlin.compose.navigation.contents.Screen +import com.google.firebase.quickstart.database.kotlin.compose.flowcontrol.LogInStatus +import com.google.firebase.quickstart.database.kotlin.theme.RealtimeDatabaseTheme + +@Composable +fun LoginPage(rootNavController: NavHostController) { + RealtimeDatabaseTheme { + Surface( + modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background + ) { + Authenticate(rootNavController) + } + } +} + + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@Composable +fun Authenticate( + rootNavController: NavHostController, + authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory), + databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory) +) { + val database = databaseProviderViewModel.database + val loginFlow = authProviderViewModel.loginFlow.collectAsState() + val signUpFlow = authProviderViewModel.signUpFlow.collectAsState() + + ComposableLifecycle { source, event -> + if (event == Lifecycle.Event.ON_START) { + authProviderViewModel.auth.currentUser?.let { + authProviderViewModel.onAuthSuccess(it, database) + rootNavController.navigate(Screen.HomeScreen.route) + } + } + } + + val email = remember { mutableStateOf("") } + val password = remember { mutableStateOf("") } + + + val context = LocalContext.current + + Scaffold( + modifier = Modifier + .fillMaxSize() + ) + { + it + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painterResource(R.drawable.firebase_lockup_400), + contentDescription = "Firebase logo", + contentScale = ContentScale.Crop, + ) + Row() + { + OutlinedTextField( + modifier = Modifier.width(150.dp), + value = email.value, + onValueChange = { email.value = it }, + label = { Text("email") } + ) + OutlinedTextField( + modifier = Modifier.width(150.dp), + label = { Text(text = "password") }, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + value = password.value, + onValueChange = { + password.value = it + }, + ) + } + Row() + { + Button( + onClick = { + authProviderViewModel.signIn(email.value, password.value, database) + }, + modifier = Modifier.width(150.dp) + ) { + Text("SignIn") + } + login(loginFlow, rootNavController) + Button( + onClick = { + authProviderViewModel.signUp(email.value, password.value, database) + }, + modifier = Modifier.width(150.dp) + ) { + Text("SignUp") + } + signUp(signUpFlow,rootNavController) + } + } + } +} + +@Composable +fun ComposableLifecycle( + lifeCycleOwner: LifecycleOwner = LocalLifecycleOwner.current, + onEvent: (LifecycleOwner, Lifecycle.Event) -> Unit +) { + DisposableEffect(lifeCycleOwner) { + val observer = LifecycleEventObserver { source, event -> + onEvent(source, event) + } + lifeCycleOwner.lifecycle.addObserver(observer) + onDispose { + lifeCycleOwner.lifecycle.removeObserver(observer) + } + } +} + +@Composable +fun login( + logInStatus: State?>, + rootNavController: NavHostController, +) { + + logInStatus?.value?.let { status -> + when (status) { + is LogInStatus.Failure -> { + val context = LocalContext.current + LaunchedEffect(Unit) { + Toast.makeText(context, "Log in failed: " + status.exception.message, Toast.LENGTH_LONG).show() + } + } + is LogInStatus.Success -> { + LaunchedEffect(Unit) { + rootNavController.navigate(Screen.HomeScreen.route) + } + } + is LogInStatus.InitState -> {} + } + + } +} + +@Composable +fun signUp( + signUpStatus: State?>, + rootNavController: NavHostController, +) { + + signUpStatus?.value?.let { status -> + when (status) { + is LogInStatus.Failure -> { + val context = LocalContext.current + LaunchedEffect(Unit) { + Toast.makeText(context, "Signup failed: " + status.exception.message, Toast.LENGTH_SHORT).show() + } + } + is LogInStatus.Success -> { + LaunchedEffect(Unit) { + rootNavController.navigate(Screen.HomeScreen.route) + } + } + is LogInStatus.InitState -> {} + } + + } +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainActivity.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainActivity.kt new file mode 100644 index 000000000..e4ac916fe --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainActivity.kt @@ -0,0 +1,29 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.ui.Modifier +import com.google.firebase.quickstart.database.kotlin.compose.navigation.contents.Navigation +import com.google.firebase.quickstart.database.kotlin.theme.RealtimeDatabaseTheme + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + RealtimeDatabaseTheme{ + Surface( + modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background + ) { + Navigation() + } + } + } + } +} + diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt new file mode 100644 index 000000000..b9e9bd616 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt @@ -0,0 +1,103 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.FloatingActionButton +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Create +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.google.firebase.quickstart.database.R +import com.google.firebase.quickstart.database.kotlin.compose.navigation.contents.Screen +import com.google.firebase.quickstart.database.kotlin.compose.topbar.TabLayout + +@Composable +fun MainPage(rootNavController: NavHostController) { + + val databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory) + val authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory) + + if(authProviderViewModel.auth.currentUser == null){return} + + Scaffold( + topBar = { + TopAppBar( + backgroundColor = colorResource(R.color.colorPrimary) + ) { + LogOutMenu(authProviderViewModel,rootNavController) + } + }, + content = { + TabLayout(rootNavController,databaseProviderViewModel,authProviderViewModel) + Column( + modifier = Modifier.padding(it) + ) { + } + }, + floatingActionButton = { + FloatingActionButton( + onClick = { rootNavController.navigate(Screen.NewPostScreen.route) }, + backgroundColor = Color.Cyan + ) { + Icon(Icons.Filled.Create, "Write post") + } + } + ) +} + +@Composable +fun LogOutMenu(authProviderViewModel: AuthProviderViewModel, + rootNavController: NavHostController) { + var showMenu by remember { mutableStateOf(false) } + + TopAppBar( + title = { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.h6, + textAlign = TextAlign.Center, + modifier = Modifier.padding(8.dp), + color = Color.White + ) + }, + actions = { + IconButton(onClick = { + showMenu = !showMenu + + }) { + Icon(Icons.Default.MoreVert, "Settings") + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false } + ) { + DropdownMenuItem(onClick = { + authProviderViewModel.auth.signOut() + rootNavController.navigate(Screen.AuthScreen.route) + }) { + Text(text = "Logout") + } + } + } + ) +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MyPostsPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MyPostsPage.kt new file mode 100644 index 000000000..95585625c --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MyPostsPage.kt @@ -0,0 +1,37 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController + +@Composable +fun MyPostsPage( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel +) { + Scaffold( + content = { + Column( + modifier = androidx.compose.ui.Modifier.padding(it) + ) { + MyPosts(rootNavController, databaseProviderViewModel,authProviderViewModel) + } + }, + ) +} + +@Composable +fun MyPosts( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel, +) { + + val uid = authProviderViewModel.auth.uid!! + val ref = databaseProviderViewModel.database.reference.child("user-posts").child(uid) + + displayList(databaseProviderViewModel, uid, rootNavController, ref) +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt new file mode 100644 index 000000000..3734d8a20 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt @@ -0,0 +1,82 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.FloatingActionButton +import androidx.compose.material.Icon +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.google.firebase.quickstart.database.kotlin.compose.navigation.contents.Screen + +@Composable +fun NewPostPage( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory) +) { + Scaffold( + content = { + Column( + modifier = androidx.compose.ui.Modifier.padding(it) + ) { + NewPost(databaseProviderViewModel) + } + }, + floatingActionButton = { + FloatingActionButton( + onClick = { + databaseProviderViewModel.submitPost() + rootNavController.navigate(Screen.HomeScreen.route) + }, + backgroundColor = Color.Cyan + ) { + Icon(Icons.Filled.Check, "Upload post") + } + } + ) +} + +@Composable +fun NewPost( + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory) +) { + + val title = remember { mutableStateOf("") } + val body = remember { mutableStateOf("") } + val uid = authProviderViewModel.auth.uid!! + + Column( + modifier = Modifier.fillMaxSize() + ) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = title.value, + onValueChange = { + title.value = it + databaseProviderViewModel.setContent(title.value, body.value, uid) + }, + label = { Text("Title") } + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = body.value, + onValueChange = { + body.value = it + databaseProviderViewModel.setContent(title.value, body.value, uid) + }, + label = { Text("Write your post") } + ) + } +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt new file mode 100644 index 000000000..1eb184ace --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt @@ -0,0 +1,170 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import android.annotation.SuppressLint +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.Card +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.google.firebase.quickstart.database.R +import com.google.firebase.quickstart.database.kotlin.compose.navigation.contents.Screen + + +@SuppressLint("RestrictedApi") +@Composable +fun PostDetailPage( + rootNavController: NavHostController, +) { + + BackHandler { + rootNavController.navigate(Screen.HomeScreen.route) + } + + + + Scaffold( + content = { + Column( + modifier = Modifier.padding(it) + ) { + PostDetail(rootNavController) + } + }, + ) +} + +@Composable +fun PostDetail( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory), + authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory) +) { + val comment = remember { mutableStateOf("") } + + // Extracting the argument + val commentId = rootNavController.currentBackStackEntry?.arguments?.getString("uid") + val post = databaseProviderViewModel.getPostDetail(commentId!!) + databaseProviderViewModel.initCommentLists(commentId) + + val uid = authProviderViewModel.auth.uid!! + val commentList = databaseProviderViewModel.getCommentLists() + + Column { + Card( + shape = RectangleShape, + modifier = Modifier + .padding(5.dp, 20.dp, 5.dp, 2.dp) + .fillMaxWidth(), + ) { + Column( + modifier = Modifier + .padding(5.dp, 2.dp, 5.dp, 2.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painterResource(R.drawable.ic_action_account_circle_40), + contentDescription = "Firebase user", + contentScale = ContentScale.Crop, + ) + Spacer(modifier = Modifier.width(10.dp)) + Text(text = post.author.toString()) + Spacer(modifier = Modifier.width(200.dp)) + } + Spacer(modifier = Modifier.height(20.dp)) + Row { + Text( + text = post.title.toString(), + fontWeight = FontWeight.Bold + ) + } + Spacer(modifier = Modifier.height(15.dp)) + Row { + Text(text = post.body.toString()) + } + } + } + Column( + modifier = Modifier.padding(5.dp, 5.dp, 5.dp, 2.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + OutlinedTextField( + value = comment.value, + onValueChange = { comment.value = it }, + label = { Text("Write a comment") }, + + ) + ClickableText( + text = AnnotatedString("POST"), + onClick = { + databaseProviderViewModel.postComment(commentId, uid, comment.value) + comment.value = "" + }, + modifier = Modifier.padding(20.dp, 0.dp, 0.dp, 0.dp) + ) + } + } + + LazyColumn { + items(commentList) { comments -> + Card( + modifier = Modifier + .fillMaxWidth() + .padding(5.dp, 5.dp, 5.dp, 0.dp), + border = BorderStroke(0.dp, Color.Transparent) + ) { + Column( + Modifier.fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painterResource(R.drawable.ic_action_account_circle_40), + contentDescription = "Firebase user", + contentScale = ContentScale.Crop, + ) + Text(text = comments.author.toString()) + } + Spacer(modifier = Modifier.width(15.dp)) + Text( + text = comments.text.toString(), + modifier = Modifier.padding(5.dp, 0.dp, 0.dp, 0.dp) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/TopPostPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/TopPostPage.kt new file mode 100644 index 000000000..d7bc7b7c0 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/TopPostPage.kt @@ -0,0 +1,44 @@ +package com.google.firebase.quickstart.database.kotlin.compose + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController + +@Composable +fun TopPostPage( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel +) { + + Scaffold( + content = { + Column( + modifier = Modifier.padding(it) + ) { + TopPostDetail(rootNavController, databaseProviderViewModel, authProviderViewModel) + } + }, + ) +} + +@Composable +fun TopPostDetail( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel, +) { + + val uid = authProviderViewModel.auth.uid!! + val ref = databaseProviderViewModel.database.reference.child("user-posts").child(uid) + val allPosts = databaseProviderViewModel.getPosts(ref) + + allPosts.sortByDescending { post -> post.starCount } + + if (allPosts != null) { + displayList(databaseProviderViewModel, uid, rootNavController, ref) + } +} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LoadDataStatus.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LoadDataStatus.kt new file mode 100644 index 000000000..a97433852 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LoadDataStatus.kt @@ -0,0 +1,9 @@ +package com.google.firebase.quickstart.database.kotlin.compose.flowcontrol + +import java.lang.Exception + +sealed class LoadDataStatus { + class Loaded(): LoadDataStatus() + class Failed(val exception: Exception): LoadDataStatus() + object Loading : LoadDataStatus() +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LogInStatus.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LogInStatus.kt new file mode 100644 index 000000000..18e045057 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/flowcontrol/LogInStatus.kt @@ -0,0 +1,9 @@ +package com.google.firebase.quickstart.database.kotlin.compose.flowcontrol + +import java.lang.Exception + +sealed class LogInStatus { + class Success : LogInStatus() + class Failure(val exception: Exception): LogInStatus() + object InitState : LogInStatus() +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt new file mode 100644 index 000000000..ba3f0dcaa --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt @@ -0,0 +1,46 @@ +package com.google.firebase.quickstart.database.kotlin.compose.navigation.contents + +import androidx.compose.runtime.Composable +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.google.firebase.quickstart.database.kotlin.compose.LoginPage +import com.google.firebase.quickstart.database.kotlin.compose.MainPage +import com.google.firebase.quickstart.database.kotlin.compose.NewPostPage +import com.google.firebase.quickstart.database.kotlin.compose.PostDetailPage +import com.google.firebase.quickstart.database.kotlin.theme.RealtimeDatabaseTheme + +@Composable +fun Navigation( + startDestination: String = Screen.AuthScreen.route, +) { + val rootNavController = rememberNavController() + + RealtimeDatabaseTheme( + ) { + NavHost( + navController = rootNavController, + startDestination = startDestination, + ) { + + composable(route = Screen.HomeScreen.route) { + MainPage(rootNavController = rootNavController) + } + + composable(route = Screen.AuthScreen.route) { + LoginPage(rootNavController = rootNavController) + } + + composable(route = Screen.NewPostScreen.route) { + //val parentViewModel = hiltViewModel() + NewPostPage(rootNavController = rootNavController) + } + + composable(route = Screen.CommentScreen.route){ + PostDetailPage(rootNavController= rootNavController) + } + + } + + } +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Screen.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Screen.kt new file mode 100644 index 000000000..4f1dae71a --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Screen.kt @@ -0,0 +1,11 @@ +package com.google.firebase.quickstart.database.kotlin.compose.navigation.contents + + + +open class Screen(val route: String) { + + object AuthScreen: Screen("auth_screen") + object HomeScreen: Screen("home_screen") + object NewPostScreen: Screen("new_post_screen") + object CommentScreen: Screen("comment_section") +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/topbar/TabLayout.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/topbar/TabLayout.kt new file mode 100644 index 000000000..e779f285a --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/topbar/TabLayout.kt @@ -0,0 +1,106 @@ +package com.google.firebase.quickstart.database.kotlin.compose.topbar + +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.Tab +import androidx.compose.material.TabRow +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.google.firebase.quickstart.database.kotlin.compose.AuthProviderViewModel +import com.google.firebase.quickstart.database.kotlin.compose.DatabaseProviderViewModel +import com.google.firebase.quickstart.database.kotlin.compose.HomePage +import com.google.firebase.quickstart.database.kotlin.compose.MyPostsPage +import com.google.firebase.quickstart.database.kotlin.compose.TopPostPage +import com.google.firebase.quickstart.database.kotlin.compose.flowcontrol.LoadDataStatus + + +@Composable +fun TabLayout( + navController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel +) { + + val dataFlow = databaseProviderViewModel.dataLoadFlow.collectAsState() + + var tabIndex by remember { mutableIntStateOf(0) } + + val tabs = listOf("Recent", "My Post", "My Top Posts") + + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.height(5.dp)) + ShowProgressBar(dataFlow = dataFlow) + Spacer(modifier = Modifier.height(10.dp)) + TabRow( + selectedTabIndex = tabIndex, + backgroundColor = Color.White, + divider = {} + ) { + tabs.forEachIndexed { index, title -> + Tab(text = { Text(title) }, + selected = tabIndex == index, + onClick = { tabIndex = index } + ) + } + } + when (tabIndex) { + 0 -> { + HomePage(navController, databaseProviderViewModel, authProviderViewModel) + } + + 1 -> { + MyPostsPage(navController, databaseProviderViewModel, authProviderViewModel) + } + + 2 -> { + TopPostPage(navController, databaseProviderViewModel, authProviderViewModel) + } + } + } +} + +@Composable +fun ShowProgressBar(dataFlow: State?>) { + + dataFlow?.value?.let { status -> + when (status) { + is LoadDataStatus.Failed -> { + val context = LocalContext.current + LaunchedEffect(Unit) { + Toast.makeText(context, status.exception.message, Toast.LENGTH_LONG).show() + + } + } + + is LoadDataStatus.Loaded -> {} + LoadDataStatus.Loading -> { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + + ) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } + } + } + + } +} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyPostsFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyPostsFragment.kt deleted file mode 100644 index d13860bb1..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyPostsFragment.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin.listfragments - -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.Query - -class MyPostsFragment : PostListFragment() { - - override fun getQuery(databaseReference: DatabaseReference): Query { - // All my posts - return databaseReference.child("user-posts") - .child(uid) - } -} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyTopPostsFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyTopPostsFragment.kt deleted file mode 100644 index 51de89682..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyTopPostsFragment.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin.listfragments - -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.Query - -class MyTopPostsFragment : PostListFragment() { - - override fun getQuery(databaseReference: DatabaseReference): Query { - // My top posts by number of stars - val myUserId = uid - - return databaseReference.child("user-posts").child(myUserId) - .orderByChild("starCount") - } -} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/PostListFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/PostListFragment.kt deleted file mode 100644 index 3e9dcba16..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/PostListFragment.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin.listfragments - -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment -import androidx.navigation.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.firebase.ui.database.FirebaseRecyclerAdapter -import com.firebase.ui.database.FirebaseRecyclerOptions -import com.google.firebase.auth.ktx.auth -import com.google.firebase.database.DataSnapshot -import com.google.firebase.database.DatabaseError -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.MutableData -import com.google.firebase.database.Query -import com.google.firebase.database.Transaction -import com.google.firebase.database.ktx.database -import com.google.firebase.ktx.Firebase -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.kotlin.PostDetailFragment -import com.google.firebase.quickstart.database.kotlin.models.Post -import com.google.firebase.quickstart.database.kotlin.viewholder.PostViewHolder - -abstract class PostListFragment : Fragment() { - - // [START define_database_reference] - private lateinit var database: DatabaseReference - // [END define_database_reference] - - private lateinit var recycler: RecyclerView - private lateinit var manager: LinearLayoutManager - private var adapter: FirebaseRecyclerAdapter? = null - - val uid: String - get() = Firebase.auth.currentUser!!.uid - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - super.onCreateView(inflater, container, savedInstanceState) - val rootView = inflater.inflate(R.layout.fragment_all_posts, container, false) - - // [START create_database_reference] - database = Firebase.database.reference - // [END create_database_reference] - - recycler = rootView.findViewById(R.id.messagesList) - recycler.setHasFixedSize(true) - - return rootView - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - // Set up Layout Manager, reverse layout - manager = LinearLayoutManager(activity) - manager.reverseLayout = true - manager.stackFromEnd = true - recycler.layoutManager = manager - - // Set up FirebaseRecyclerAdapter with the Query - val postsQuery = getQuery(database) - - val options = FirebaseRecyclerOptions.Builder() - .setQuery(postsQuery, Post::class.java) - .build() - - adapter = object : FirebaseRecyclerAdapter(options) { - - override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PostViewHolder { - val inflater = LayoutInflater.from(viewGroup.context) - return PostViewHolder(inflater.inflate(R.layout.item_post, viewGroup, false)) - } - - override fun onBindViewHolder(viewHolder: PostViewHolder, position: Int, model: Post) { - val postRef = getRef(position) - - // Set click listener for the whole post view - val postKey = postRef.key - viewHolder.itemView.setOnClickListener { - // Launch PostDetailFragment - val navController = requireActivity().findNavController(R.id.nav_host_fragment) - val args = bundleOf(PostDetailFragment.EXTRA_POST_KEY to postKey) - navController.navigate(R.id.action_MainFragment_to_PostDetailFragment, args) - } - - // Determine if the current user has liked this post and set UI accordingly - viewHolder.setLikedState(model.stars.containsKey(uid)) - - // Bind Post to ViewHolder, setting OnClickListener for the star button - viewHolder.bindToPost(model) { - // Need to write to both places the post is stored - val globalPostRef = database.child("posts").child(postRef.key!!) - val userPostRef = database.child("user-posts").child(model.uid!!).child(postRef.key!!) - - // Run two transactions - onStarClicked(globalPostRef) - onStarClicked(userPostRef) - } - } - } - recycler.adapter = adapter - } - - // [START post_stars_transaction] - private fun onStarClicked(postRef: DatabaseReference) { - postRef.runTransaction(object : Transaction.Handler { - override fun doTransaction(mutableData: MutableData): Transaction.Result { - val p = mutableData.getValue(Post::class.java) - ?: return Transaction.success(mutableData) - - if (p.stars.containsKey(uid)) { - // Unstar the post and remove self from stars - p.starCount = p.starCount - 1 - p.stars.remove(uid) - } else { - // Star the post and add self to stars - p.starCount = p.starCount + 1 - p.stars[uid] = true - } - - // Set value and report transaction success - mutableData.value = p - return Transaction.success(mutableData) - } - - override fun onComplete( - databaseError: DatabaseError?, - committed: Boolean, - currentData: DataSnapshot? - ) { - // Transaction completed - Log.d(TAG, "postTransaction:onComplete:" + databaseError!!) - } - }) - } - // [END post_stars_transaction] - - override fun onStart() { - super.onStart() - adapter?.startListening() - } - - override fun onStop() { - super.onStop() - adapter?.stopListening() - } - - abstract fun getQuery(databaseReference: DatabaseReference): Query - - companion object { - - private const val TAG = "PostListFragment" - } -} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/RecentPostsFragment.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/RecentPostsFragment.kt deleted file mode 100644 index e64488e13..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/RecentPostsFragment.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin.listfragments - -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.Query - -class RecentPostsFragment : PostListFragment() { - - override fun getQuery(databaseReference: DatabaseReference): Query { - // [START recent_posts_query] - // Last 100 posts, these are automatically the 100 most recent - // due to sorting by push() keys. - return databaseReference.child("posts") - .limitToFirst(100) - // [END recent_posts_query] - } -} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/models/Post.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/models/Post.kt index b583973fa..72d955305 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/models/Post.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/models/Post.kt @@ -6,6 +6,7 @@ import java.util.HashMap @IgnoreExtraProperties data class Post( + var key: String? = "", var uid: String? = "", var author: String? = "", var title: String? = "", @@ -17,12 +18,13 @@ data class Post( @Exclude fun toMap(): Map { return mapOf( - "uid" to uid, - "author" to author, - "title" to title, - "body" to body, - "starCount" to starCount, - "stars" to stars + "key" to key, + "uid" to uid, + "author" to author, + "title" to title, + "body" to body, + "starCount" to starCount, + "stars" to stars ) } } diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Color.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Color.kt new file mode 100644 index 000000000..a4c117e0d --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Color.kt @@ -0,0 +1,10 @@ +package com.google.firebase.quickstart.database.kotlin.theme +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val FirebaseBlue = Color(0xFF0288D1) // copied from colors.xml +val FirebaseBannerBlue = Color(0xFF039BE5) // copied from colors.xml +val FirebaseOrange = Color(0xFFFFA000) // copied from colors.xml \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Shape.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Shape.kt new file mode 100644 index 000000000..2ba11bf0d --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.google.firebase.quickstart.database.kotlin.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(16.dp), + medium = RoundedCornerShape(2.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Theme.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Theme.kt new file mode 100644 index 000000000..aa675b4c3 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Theme.kt @@ -0,0 +1,39 @@ +package com.google.firebase.quickstart.database.kotlin.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors + +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple80, + primaryVariant = PurpleGrey80, + secondary = Pink80 +) + +private val LightColorPalette = lightColors( + primary = FirebaseBlue, + primaryVariant = FirebaseBannerBlue, + secondary = FirebaseOrange +) + +@Composable +fun RealtimeDatabaseTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Type.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Type.kt new file mode 100644 index 000000000..285e6e345 --- /dev/null +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/theme/Type.kt @@ -0,0 +1,18 @@ +package com.google.firebase.quickstart.database.kotlin.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/CommentViewHolder.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/CommentViewHolder.kt deleted file mode 100644 index 07be868c3..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/CommentViewHolder.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin.viewholder - -import android.view.View -import android.widget.TextView -import androidx.recyclerview.widget.RecyclerView -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.kotlin.models.Comment - -class CommentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - fun bind(comment: Comment) { - itemView.findViewById(R.id.commentAuthor).text = comment.author - itemView.findViewById(R.id.commentBody).text = comment.text - } -} \ No newline at end of file diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/PostViewHolder.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/PostViewHolder.kt deleted file mode 100644 index b407d7935..000000000 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/PostViewHolder.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.google.firebase.quickstart.database.kotlin.viewholder - -import androidx.recyclerview.widget.RecyclerView -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import com.google.firebase.quickstart.database.R -import com.google.firebase.quickstart.database.kotlin.models.Post - -class PostViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - private val postTitle: TextView = itemView.findViewById(R.id.postTitle) - private val postAuthor: TextView = itemView.findViewById(R.id.postAuthor) - private val postNumStars: TextView = itemView.findViewById(R.id.postNumStars) - private val postBody: TextView = itemView.findViewById(R.id.postBody) - private val star: ImageView = itemView.findViewById(R.id.star) - - fun bindToPost(post: Post, starClickListener: View.OnClickListener) { - postTitle.text = post.title - postAuthor.text = post.author - postNumStars.text = post.starCount.toString() - postBody.text = post.body - - star.setOnClickListener(starClickListener) - } - - fun setLikedState(liked: Boolean) { - if (liked) { - star.setImageResource(R.drawable.ic_toggle_star_24) - } else { - star.setImageResource(R.drawable.ic_toggle_star_outline_24) - } - } -} diff --git a/database/build.gradle b/database/build.gradle index 1554d788e..93b20b76b 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -11,6 +11,9 @@ buildscript { classpath 'com.google.gms:google-services:4.3.14' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } + ext { + compose_version = '1.2.1' + } } allprojects { From cc3e71359806a2a448f280a60f5bedfad061312c Mon Sep 17 00:00:00 2001 From: alcantaraalvin Date: Tue, 23 Jul 2024 21:19:30 +0800 Subject: [PATCH 2/4] -Restructured the code to get only one instance of viewmodels on all composables/fragments -Removed unnecessary variables -Prevent the Textfields on login screen to be resizable --- .../compose/DatabaseProviderViewModel.kt | 9 +++ .../database/kotlin/compose/HomePage.kt | 11 ++-- .../database/kotlin/compose/LoginPage.kt | 27 ++++++--- .../database/kotlin/compose/MainPage.kt | 9 +-- .../database/kotlin/compose/NewPostPage.kt | 7 ++- .../database/kotlin/compose/PostDetailPage.kt | 15 ++--- .../compose/navigation/contents/Navigation.kt | 15 +++-- .../main/res/navigation/nav_graph_kotlin.xml | 56 ------------------- 8 files changed, 58 insertions(+), 91 deletions(-) delete mode 100644 database/app/src/main/res/navigation/nav_graph_kotlin.xml diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt index a950f0594..7308baa30 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/DatabaseProviderViewModel.kt @@ -35,6 +35,7 @@ class DatabaseProviderViewModel( private val postList = mutableStateListOf() private val postDetails = mutableStateOf(Post()) private val commentList = mutableStateListOf() + private val postId = mutableStateOf("") //variables for flow of loading data private val _dataLoadFlow = MutableStateFlow?>(null) @@ -246,6 +247,14 @@ class DatabaseProviderViewModel( } + fun getPostID():String{ + return postId.value + } + + fun setPostID(id: String){ + postId.value = id + } + } diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt index a9f90d0e1..cf66eb7b2 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/HomePage.kt @@ -97,7 +97,7 @@ fun displayList( .padding(1.dp, 2.dp, 5.dp, 0.dp) .fillMaxWidth(), onClick = { - onCardClick(rootNavController, post.key!!) + onCardClick(rootNavController, post.key!!,databaseProviderViewModel) } ) { Column( @@ -166,11 +166,8 @@ fun onStarClick(databaseProviderViewModel: DatabaseProviderViewModel, uid: Strin } @SuppressLint("RestrictedApi") -fun onCardClick(rootNavController: NavHostController, uid: String) { +fun onCardClick(rootNavController: NavHostController, uid: String, databaseProviderViewModel: DatabaseProviderViewModel) { - val bundle = bundleOf("uid" to uid) - val destination = rootNavController.findDestination(Screen.CommentScreen.route) - if (destination != null) { - rootNavController.navigate(destination.id, bundle) - } + databaseProviderViewModel.setPostID(uid) + rootNavController.navigate(Screen.CommentScreen.route) } diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt index 68312ff75..1670f360f 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/LoginPage.kt @@ -6,8 +6,10 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Button @@ -43,12 +45,16 @@ import com.google.firebase.quickstart.database.kotlin.compose.flowcontrol.LogInS import com.google.firebase.quickstart.database.kotlin.theme.RealtimeDatabaseTheme @Composable -fun LoginPage(rootNavController: NavHostController) { +fun LoginPage( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel +) { RealtimeDatabaseTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { - Authenticate(rootNavController) + Authenticate(rootNavController,databaseProviderViewModel,authProviderViewModel) } } } @@ -58,8 +64,8 @@ fun LoginPage(rootNavController: NavHostController) { @Composable fun Authenticate( rootNavController: NavHostController, - authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory), - databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory) + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel ) { val database = databaseProviderViewModel.database val loginFlow = authProviderViewModel.loginFlow.collectAsState() @@ -77,9 +83,6 @@ fun Authenticate( val email = remember { mutableStateOf("") } val password = remember { mutableStateOf("") } - - val context = LocalContext.current - Scaffold( modifier = Modifier .fillMaxSize() @@ -100,13 +103,18 @@ fun Authenticate( Row() { OutlinedTextField( - modifier = Modifier.width(150.dp), + modifier = Modifier + .width(150.dp) + .height(60.dp), value = email.value, onValueChange = { email.value = it }, label = { Text("email") } ) + Spacer(modifier = Modifier.width(2.dp)) OutlinedTextField( - modifier = Modifier.width(150.dp), + modifier = Modifier + .width(150.dp) + .height(60.dp), label = { Text(text = "password") }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), @@ -127,6 +135,7 @@ fun Authenticate( Text("SignIn") } login(loginFlow, rootNavController) + Spacer(modifier = Modifier.width(2.dp)) Button( onClick = { authProviderViewModel.signUp(email.value, password.value, database) diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt index b9e9bd616..79fb34249 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/MainPage.kt @@ -32,10 +32,11 @@ import com.google.firebase.quickstart.database.kotlin.compose.navigation.content import com.google.firebase.quickstart.database.kotlin.compose.topbar.TabLayout @Composable -fun MainPage(rootNavController: NavHostController) { - - val databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory) - val authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory) +fun MainPage( + rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel +) { if(authProviderViewModel.auth.currentUser == null){return} diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt index 3734d8a20..b08cf78b0 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/NewPostPage.kt @@ -23,14 +23,15 @@ import com.google.firebase.quickstart.database.kotlin.compose.navigation.content @Composable fun NewPostPage( rootNavController: NavHostController, - databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory) + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel ) { Scaffold( content = { Column( modifier = androidx.compose.ui.Modifier.padding(it) ) { - NewPost(databaseProviderViewModel) + NewPost(databaseProviderViewModel,authProviderViewModel) } }, floatingActionButton = { @@ -50,7 +51,7 @@ fun NewPostPage( @Composable fun NewPost( databaseProviderViewModel: DatabaseProviderViewModel, - authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory) + authProviderViewModel: AuthProviderViewModel ) { val title = remember { mutableStateOf("") } diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt index 1eb184ace..569ca0230 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/PostDetailPage.kt @@ -41,10 +41,12 @@ import com.google.firebase.quickstart.database.kotlin.compose.navigation.content @Composable fun PostDetailPage( rootNavController: NavHostController, + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel, ) { - BackHandler { - rootNavController.navigate(Screen.HomeScreen.route) + BackHandler { + rootNavController.navigate(Screen.HomeScreen.route) } @@ -54,7 +56,7 @@ fun PostDetailPage( Column( modifier = Modifier.padding(it) ) { - PostDetail(rootNavController) + PostDetail(rootNavController, databaseProviderViewModel, authProviderViewModel) } }, ) @@ -63,13 +65,12 @@ fun PostDetailPage( @Composable fun PostDetail( rootNavController: NavHostController, - databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory), - authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory) + databaseProviderViewModel: DatabaseProviderViewModel, + authProviderViewModel: AuthProviderViewModel ) { val comment = remember { mutableStateOf("") } - // Extracting the argument - val commentId = rootNavController.currentBackStackEntry?.arguments?.getString("uid") + val commentId = databaseProviderViewModel.getPostID() val post = databaseProviderViewModel.getPostDetail(commentId!!) databaseProviderViewModel.initCommentLists(commentId) diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt index ba3f0dcaa..8e609bf7e 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/navigation/contents/Navigation.kt @@ -1,9 +1,12 @@ package com.google.firebase.quickstart.database.kotlin.compose.navigation.contents import androidx.compose.runtime.Composable +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.google.firebase.quickstart.database.kotlin.compose.AuthProviderViewModel +import com.google.firebase.quickstart.database.kotlin.compose.DatabaseProviderViewModel import com.google.firebase.quickstart.database.kotlin.compose.LoginPage import com.google.firebase.quickstart.database.kotlin.compose.MainPage import com.google.firebase.quickstart.database.kotlin.compose.NewPostPage @@ -16,6 +19,9 @@ fun Navigation( ) { val rootNavController = rememberNavController() + val databaseProviderViewModel: DatabaseProviderViewModel = viewModel(factory = DatabaseProviderViewModel.Factory) + val authProviderViewModel: AuthProviderViewModel = viewModel(factory = AuthProviderViewModel.Factory) + RealtimeDatabaseTheme( ) { NavHost( @@ -24,20 +30,19 @@ fun Navigation( ) { composable(route = Screen.HomeScreen.route) { - MainPage(rootNavController = rootNavController) + MainPage(rootNavController = rootNavController, databaseProviderViewModel, authProviderViewModel ) } composable(route = Screen.AuthScreen.route) { - LoginPage(rootNavController = rootNavController) + LoginPage(rootNavController = rootNavController, databaseProviderViewModel, authProviderViewModel) } composable(route = Screen.NewPostScreen.route) { - //val parentViewModel = hiltViewModel() - NewPostPage(rootNavController = rootNavController) + NewPostPage(rootNavController = rootNavController, databaseProviderViewModel, authProviderViewModel) } composable(route = Screen.CommentScreen.route){ - PostDetailPage(rootNavController= rootNavController) + PostDetailPage(rootNavController= rootNavController, databaseProviderViewModel, authProviderViewModel) } } diff --git a/database/app/src/main/res/navigation/nav_graph_kotlin.xml b/database/app/src/main/res/navigation/nav_graph_kotlin.xml deleted file mode 100644 index 8d63bb716..000000000 --- a/database/app/src/main/res/navigation/nav_graph_kotlin.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 2c4634dc42aad4298d49f46d9ad0f4b74605fea5 Mon Sep 17 00:00:00 2001 From: alcantaraalvin Date: Tue, 23 Jul 2024 21:33:08 +0800 Subject: [PATCH 3/4] -Revert the changes on proguard file --- database/app/proguard-rules.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/app/proguard-rules.pro b/database/app/proguard-rules.pro index 5ceba5ccc..c0abaac4d 100644 --- a/database/app/proguard-rules.pro +++ b/database/app/proguard-rules.pro @@ -29,6 +29,6 @@ *; } --keepclassmembers class com.google.firebase.quickstart.database.kotlin_old.models.** { +-keepclassmembers class com.google.firebase.quickstart.database.kotlin.models.** { *; } From ba247a90dbd1b7bde066f5a4868943a50449957a Mon Sep 17 00:00:00 2001 From: alcantaraalvin Date: Wed, 24 Jul 2024 06:16:45 +0800 Subject: [PATCH 4/4] -Optimized validateForm function on AuthProviderViewModel --- .../kotlin/compose/AuthProviderViewModel.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt index d59706101..2981d4dd5 100644 --- a/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt +++ b/database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/compose/AuthProviderViewModel.kt @@ -41,18 +41,7 @@ class AuthProviderViewModel( } fun validateForm(email: String, password: String): Boolean { - var result = true - if (email.isEmpty()) { - result = false - } else { - } - - if (password.isEmpty()) { - result = false - } else { - } - - return result + return email.isNotEmpty() && password.isNotEmpty() }