diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 43c9c29f..c42fee0d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -194,7 +194,7 @@ android:label="Inbox Activity" android:parentActivityName=".MainActivity" /> - private lateinit var subscriptionStore: List - private var adapter: GroupieAdapter = GroupieAdapter() - private var notificationList: List = emptyList() - val filters = ArrayList() - private var currentPage: Int = 1 - private var hasNextPage: Boolean = true - - @SuppressLint("ClickableViewAccessibility") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ThemeManager(this).applyTheme() - initActivity(this) - binding = ActivityFollowBinding.inflate(layoutInflater) - setContentView(binding.root) - binding.listTitle.text = getString(R.string.notifications) - binding.listToolbar.updateLayoutParams { - topMargin = statusBarHeight - } - binding.listFrameLayout.updateLayoutParams { - bottomMargin = navBarHeight - } - binding.listRecyclerView.adapter = adapter - binding.listRecyclerView.layoutManager = - LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) - binding.followerGrid.visibility = ViewGroup.GONE - binding.followerList.visibility = ViewGroup.GONE - binding.listBack.setOnClickListener { - onBackPressedDispatcher.onBackPressed() - } - binding.listProgressBar.visibility = ViewGroup.VISIBLE - commentStore = PrefManager.getNullableVal>( - PrefName.CommentNotificationStore, - null - ) ?: listOf() - subscriptionStore = PrefManager.getNullableVal>( - PrefName.SubscriptionNotificationStore, - null - ) ?: listOf() - - binding.followFilterButton.setOnClickListener { - val dialogView = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null) - val checkboxContainer = dialogView.findViewById(R.id.checkboxContainer) - val tickAllButton = dialogView.findViewById(R.id.toggleButton) - val title = dialogView.findViewById(R.id.scantitle) - title.visibility = ViewGroup.GONE - fun getToggleImageResource(container: ViewGroup): Int { - var allChecked = true - var allUnchecked = true - - for (i in 0 until container.childCount) { - val checkBox = container.getChildAt(i) as CheckBox - if (!checkBox.isChecked) { - allChecked = false - } else { - allUnchecked = false - } - } - return when { - allChecked -> R.drawable.untick_all_boxes - allUnchecked -> R.drawable.tick_all_boxes - else -> R.drawable.invert_all_boxes - } - } - NotificationType.entries.forEach { notificationType -> - val checkBox = CheckBox(currContext()) - checkBox.text = notificationType.toFormattedString() - checkBox.isChecked = !filters.contains(notificationType.value.fromFormattedString()) - checkBox.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - filters.remove(notificationType.value.fromFormattedString()) - } else { - filters.add(notificationType.value.fromFormattedString()) - } - tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) - } - checkboxContainer.addView(checkBox) - } - tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) - tickAllButton.setOnClickListener { - for (i in 0 until checkboxContainer.childCount) { - val checkBox = checkboxContainer.getChildAt(i) as CheckBox - checkBox.isChecked = !checkBox.isChecked - } - - tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) - } - val alertD = AlertDialog.Builder(this, R.style.MyPopup) - alertD.setTitle("Filter") - alertD.setView(dialogView) - alertD.setPositiveButton("OK") { _, _ -> - currentPage = 1 - hasNextPage = true - adapter.clear() - adapter.addAll(notificationList.filter { notification -> - !filters.contains(notification.notificationType) - }.map { - NotificationItem( - it, - ::onNotificationClick - ) - }) - loadPage(-1) { - binding.followRefresh.visibility = ViewGroup.GONE - } - } - alertD.setNegativeButton("Cancel") { _, _ -> } - val dialog = alertD.show() - dialog.window?.setDimAmount(0.8f) - } - - val activityId = intent.getIntExtra("activityId", -1) - lifecycleScope.launch { - loadPage(activityId) { - binding.listProgressBar.visibility = ViewGroup.GONE - } - withContext(Dispatchers.Main) { - binding.listProgressBar.visibility = ViewGroup.GONE - binding.listRecyclerView.setOnTouchListener { _, event -> - if (event?.action == MotionEvent.ACTION_UP) { - if (hasNextPage && !binding.listRecyclerView.canScrollVertically(1) && !binding.followRefresh.isVisible - && binding.listRecyclerView.adapter!!.itemCount != 0 && - (binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1) - ) { - binding.followRefresh.visibility = ViewGroup.VISIBLE - loadPage(-1) { - binding.followRefresh.visibility = ViewGroup.GONE - } - } - } - false - } - - binding.followSwipeRefresh.setOnRefreshListener { - currentPage = 1 - hasNextPage = true - adapter.clear() - notificationList = emptyList() - loadPage(-1) { - binding.followSwipeRefresh.isRefreshing = false - } - } - } - } - } - - private fun loadPage(activityId: Int, onFinish: () -> Unit = {}) { - lifecycleScope.launch(Dispatchers.IO) { - val resetNotification = activityId == -1 - val res = Anilist.query.getNotifications( - Anilist.userid ?: PrefManager.getVal(PrefName.AnilistUserId).toIntOrNull() - ?: 0, currentPage, resetNotification = resetNotification - ) - withContext(Dispatchers.Main) { - val newNotifications: MutableList = mutableListOf() - res?.data?.page?.notifications?.let { notifications -> - Logger.log("Notifications: $notifications") - newNotifications += if (activityId != -1) { - notifications.filter { it.id == activityId } - } else { - notifications - }.toMutableList() - } - if (activityId == -1) { - val furthestTime = newNotifications.minOfOrNull { it.createdAt } ?: 0 - commentStore.forEach { - if ((it.time > furthestTime * 1000L || !hasNextPage) && notificationList.none { notification -> - notification.commentId == it.commentId && notification.createdAt == (it.time / 1000L).toInt() - }) { - val notification = Notification( - it.type.toString(), - System.currentTimeMillis().toInt(), - commentId = it.commentId, - notificationType = it.type.toString(), - mediaId = it.mediaId, - context = it.title + "\n" + it.content, - createdAt = (it.time / 1000L).toInt(), - ) - newNotifications += notification - } - } - subscriptionStore.forEach { - if ((it.time > furthestTime * 1000L || !hasNextPage) && notificationList.none { notification -> - notification.mediaId == it.mediaId && notification.createdAt == (it.time / 1000L).toInt() - }) { - val notification = Notification( - it.type, - System.currentTimeMillis().toInt(), - commentId = it.mediaId, - mediaId = it.mediaId, - notificationType = it.type, - context = it.title + ": " + it.content, - createdAt = (it.time / 1000L).toInt(), - image = it.image, - banner = it.banner ?: it.image - ) - newNotifications += notification - } - } - newNotifications.sortByDescending { it.createdAt } - } - - notificationList += newNotifications - adapter.addAll(newNotifications.filter { notification -> - !filters.contains(notification.notificationType) - }.map { - NotificationItem( - it, - ::onNotificationClick - ) - }) - currentPage = res?.data?.page?.pageInfo?.currentPage?.plus(1) ?: 1 - hasNextPage = res?.data?.page?.pageInfo?.hasNextPage ?: false - binding.followSwipeRefresh.isRefreshing = false - onFinish() - } - } - } - - private fun onNotificationClick(id: Int, optional: Int?, type: NotificationClickType) { - when (type) { - NotificationClickType.USER -> { - ContextCompat.startActivity( - this, Intent(this, ProfileActivity::class.java) - .putExtra("userId", id), null - ) - } - - NotificationClickType.MEDIA -> { - ContextCompat.startActivity( - this, Intent(this, MediaDetailsActivity::class.java) - .putExtra("mediaId", id), null - ) - } - - NotificationClickType.ACTIVITY -> { - ContextCompat.startActivity( - this, Intent(this, FeedActivity::class.java) - .putExtra("activityId", id), null - ) - } - - NotificationClickType.COMMENT -> { - ContextCompat.startActivity( - this, Intent(this, MediaDetailsActivity::class.java) - .putExtra("FRAGMENT_TO_LOAD", "COMMENTS") - .putExtra("mediaId", id) - .putExtra("commentId", optional ?: -1), - null - ) - - } - - NotificationClickType.UNDEFINED -> { - // Do nothing - } - } - } - - companion object { - enum class NotificationClickType { - USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt new file mode 100644 index 00000000..8af2cf24 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt @@ -0,0 +1,94 @@ +package ani.dantotsu.profile.notification + +import android.os.Bundle +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.updateLayoutParams +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import ani.dantotsu.R +import ani.dantotsu.databinding.ActivityNotificationBinding +import ani.dantotsu.initActivity +import ani.dantotsu.navBarHeight +import ani.dantotsu.statusBarHeight +import ani.dantotsu.themes.ThemeManager +import nl.joery.animatedbottombar.AnimatedBottomBar + +class NotificationActivity : AppCompatActivity() { + private lateinit var binding: ActivityNotificationBinding + private var selected: Int = 0 + lateinit var navBar: AnimatedBottomBar + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ThemeManager(this).applyTheme() + initActivity(this) + binding = ActivityNotificationBinding.inflate(layoutInflater) + setContentView(binding.root) + binding.notificationTitle.text = getString(R.string.notifications) + binding.notificationToolbar.updateLayoutParams { + topMargin = statusBarHeight + } + navBar = binding.notificationNavBar + binding.root.updateLayoutParams { + bottomMargin = navBarHeight + } + val mediaTab = navBar.createTab(R.drawable.ic_round_movie_filter_24, "Media") + val userTab = navBar.createTab(R.drawable.ic_round_person_24, "User") + val subscriptionTab = navBar.createTab(R.drawable.ic_round_notifications_active_24, "Subscriptions") + val commentTab = navBar.createTab(R.drawable.ic_round_comment_24, "Comments") + navBar.addTab(mediaTab) + navBar.addTab(userTab) + navBar.addTab(subscriptionTab) + navBar.addTab(commentTab) + binding.notificationBack.setOnClickListener { + onBackPressedDispatcher.onBackPressed() + } + val getOne = intent.getIntExtra("activityId", -1) + binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne) + binding.notificationViewPager.setOffscreenPageLimit(4) + binding.notificationViewPager.setCurrentItem(selected, false) + binding.notificationViewPager + navBar.selectTabAt(selected) + navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { + override fun onTabSelected( + lastIndex: Int, + lastTab: AnimatedBottomBar.Tab?, + newIndex: Int, + newTab: AnimatedBottomBar.Tab + ) { + selected = newIndex + binding.notificationViewPager.setCurrentItem(selected, true) + } + }) + binding.notificationViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + navBar.selectTabAt(position) + } + }) + } + override fun onResume() { + if (this::navBar.isInitialized) { + navBar.selectTabAt(selected) + } + super.onResume() + } + private class ViewPagerAdapter( + fragmentManager: FragmentManager, + lifecycle: Lifecycle, + val id: Int = -1 + ) : FragmentStateAdapter(fragmentManager, lifecycle) { + override fun getItemCount(): Int = 4 + + override fun createFragment(position: Int): Fragment = when (position) { + 0 -> NotificationFragment(if (id != -1) "getOne" else "media", id) + 1 -> NotificationFragment("user") + 2 -> NotificationFragment("subscription") + 3 -> NotificationFragment("comment") + else -> NotificationFragment("media") + } + } +} diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt new file mode 100644 index 00000000..c6494b9d --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt @@ -0,0 +1,238 @@ +package ani.dantotsu.profile.notification + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import ani.dantotsu.R +import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.connections.anilist.api.Notification +import ani.dantotsu.databinding.FragmentNotificationsBinding +import ani.dantotsu.media.MediaDetailsActivity +import ani.dantotsu.notifications.comment.CommentStore +import ani.dantotsu.notifications.subscription.SubscriptionStore +import ani.dantotsu.profile.ProfileActivity +import ani.dantotsu.profile.activity.FeedActivity +import ani.dantotsu.setBaseline +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import com.xwray.groupie.GroupieAdapter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class NotificationFragment(val type: String, private val getID: Int = -1) : Fragment() { + private lateinit var binding: FragmentNotificationsBinding + private var adapter: GroupieAdapter = GroupieAdapter() + private var currentPage = 1 + private var hasNextPage = false + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentNotificationsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val navbar = (activity as NotificationActivity).navBar + binding.listRecyclerView.setBaseline(navbar) + binding.listRecyclerView.adapter = adapter + binding.listRecyclerView.layoutManager = LinearLayoutManager(context) + binding.emptyTextView.text = getString(R.string.no_notifications) + lifecycleScope.launch { + val list = when (type) { + "getOne" -> getOne(getID) + "media" -> getMediaUpdates() + "user" -> getUserUpdates() + "subscription" -> getSubscriptions() + "comment" -> getComments() + else -> listOf() + } + adapter.addAll(list.map { NotificationItem(it, ::onClick) }) + if (adapter.itemCount != 0) { + binding.listProgressBar.isVisible = false + }else{ + binding.listProgressBar.isVisible = false + binding.emptyTextView.isVisible = true + } + } + binding.followSwipeRefresh.setOnRefreshListener { + lifecycleScope.launch { + adapter.clear() + currentPage = 1 + val list = when (type) { + "getOne" -> getOne(getID) + "media" -> getMediaUpdates() + "user" -> getUserUpdates() + "subscription" -> getSubscriptions() + "comment" -> getComments() + else -> listOf() + } + adapter.addAll(list.map { NotificationItem(it, ::onClick) }) + binding.followSwipeRefresh.isRefreshing = false + } + } + binding.listRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + if (shouldLoadMore()) { + lifecycleScope.launch { + val list = when (type) { + "media" -> getMediaUpdates() + "user" -> getUserUpdates() + else -> listOf() + } + binding.followRefresh.visibility = View.VISIBLE + adapter.addAll(list.map { + NotificationItem( + it, + ::onClick + ) + }) + binding.followRefresh.visibility = View.GONE + } + } + } + }) + + } + + private suspend fun getNotificationsFiltered(filter: (Notification) -> Boolean): List { + val userId = Anilist.userid ?: PrefManager.getVal(PrefName.AnilistUserId).toIntOrNull() ?: 0 + val res = withContext(Dispatchers.IO) { + Anilist.query.getNotifications(userId, currentPage) + } + currentPage = res?.data?.page?.pageInfo?.currentPage?.plus(1) ?: 1 + hasNextPage = res?.data?.page?.pageInfo?.hasNextPage ?: false + return res?.data?.page?.notifications?.filter(filter) ?: listOf() + } + + private suspend fun getMediaUpdates(): List { + return getNotificationsFiltered { it.media != null } + } + + private suspend fun getUserUpdates(): List { + return getNotificationsFiltered { it.media == null } + } + + private suspend fun getOne(id: Int): List { + return getNotificationsFiltered { it.id == id } + } + + private fun getSubscriptions(): List { + val list = PrefManager.getNullableVal>( + PrefName.SubscriptionNotificationStore, + null + ) ?: listOf() + return list.sortedByDescending{ (it.time / 1000L).toInt() } + .filter{ it.image != null }.map { + Notification( + it.type, + System.currentTimeMillis().toInt(), + commentId = it.mediaId, + mediaId = it.mediaId, + notificationType = it.type, + context = it.title + ": " + it.content, + createdAt = (it.time / 1000L).toInt(), + image = it.image, + banner = it.banner ?: it.image + ) + } + } + + private fun getComments(): List { + val list = PrefManager.getNullableVal>( + PrefName.CommentNotificationStore, + null + ) ?: listOf() + return list + .sortedByDescending {(it.time / 1000L).toInt()} + .map { + Notification( + it.type.toString(), + System.currentTimeMillis().toInt(), + commentId = it.commentId, + notificationType = it.type.toString(), + mediaId = it.mediaId, + context = it.title + "\n" + it.content, + createdAt = (it.time / 1000L).toInt(), + ) + } + } + + private fun shouldLoadMore(): Boolean { + val layoutManager = binding.listRecyclerView.layoutManager as LinearLayoutManager + val adapter = binding.listRecyclerView.adapter + + return hasNextPage && !binding.followRefresh.isVisible && adapter?.itemCount != 0 && + layoutManager.findLastVisibleItemPosition() == (adapter!!.itemCount - 1) && + !binding.listRecyclerView.canScrollVertically(1) + } + + fun onClick( + id: Int, + optional: Int?, + type: NotificationClickType + ) { + when (type) { + NotificationClickType.USER -> { + ContextCompat.startActivity( + requireContext(), Intent( requireContext(), ProfileActivity::class.java) + .putExtra("userId", id), null + ) + } + + NotificationClickType.MEDIA -> { + ContextCompat.startActivity( + requireContext(), Intent( requireContext(), MediaDetailsActivity::class.java) + .putExtra("mediaId", id), null + ) + } + + NotificationClickType.ACTIVITY -> { + ContextCompat.startActivity( + requireContext(), Intent( requireContext(), FeedActivity::class.java) + .putExtra("activityId", id), null + ) + } + + NotificationClickType.COMMENT -> { + ContextCompat.startActivity( + requireContext(), Intent( requireContext(), MediaDetailsActivity::class.java) + .putExtra("FRAGMENT_TO_LOAD", "COMMENTS") + .putExtra("mediaId", id) + .putExtra("commentId", optional ?: -1), + null + ) + + } + NotificationClickType.UNDEFINED -> { + // Do nothing + } + } + } + + override fun onResume() { + super.onResume() + if (this::binding.isInitialized) { + binding.root.requestLayout() + binding.root.setBaseline((activity as NotificationActivity).navBar) + } + } + companion object { + enum class NotificationClickType { + USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt rename to app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt index d0e932d9..f51225ea 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.profile.activity +package ani.dantotsu.profile.notification import android.view.View import android.view.ViewGroup @@ -8,7 +8,8 @@ import ani.dantotsu.connections.anilist.api.Notification import ani.dantotsu.connections.anilist.api.NotificationType import ani.dantotsu.databinding.ItemNotificationBinding import ani.dantotsu.loadImage -import ani.dantotsu.profile.activity.NotificationActivity.Companion.NotificationClickType +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationClickType +import ani.dantotsu.profile.activity.ActivityItemBuilder import ani.dantotsu.setAnimation import ani.dantotsu.toPx import com.xwray.groupie.viewbinding.BindableItem diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt index 70da38d4..1072d742 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt @@ -1,6 +1,5 @@ package ani.dantotsu.settings -import android.app.AlertDialog import android.content.Intent import android.graphics.Color import android.os.Bundle @@ -16,7 +15,6 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.databinding.BottomSheetSettingsBinding import ani.dantotsu.download.anime.OfflineAnimeFragment import ani.dantotsu.download.manga.OfflineMangaFragment -import ani.dantotsu.getAppString import ani.dantotsu.getThemeColor import ani.dantotsu.home.AnimeFragment import ani.dantotsu.home.HomeFragment @@ -28,7 +26,7 @@ import ani.dantotsu.loadImage import ani.dantotsu.offline.OfflineFragment import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.activity.FeedActivity -import ani.dantotsu.profile.activity.NotificationActivity +import ani.dantotsu.profile.notification.NotificationActivity import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName diff --git a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt index d2ab6601..14e71432 100644 --- a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt +++ b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt @@ -135,6 +135,7 @@ class AlertDialogBuilder(private val context: Context) { } else if (checkedItems != null && onItemsSelected != null) { builder.setMultiChoiceItems(items, checkedItems) { _, which, isChecked -> checkedItems?.set(which, isChecked) + onItemsSelected?.invoke(checkedItems!!) } } } diff --git a/app/src/main/res/layout/activity_follow.xml b/app/src/main/res/layout/activity_follow.xml index 9ec387c4..ded5b53f 100644 --- a/app/src/main/res/layout/activity_follow.xml +++ b/app/src/main/res/layout/activity_follow.xml @@ -103,15 +103,18 @@ - + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center_horizontal"> @@ -132,8 +135,8 @@ style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="bottom|center_horizontal" android:layout_marginBottom="32dp" - android:visibility="gone" /> - + android:visibility="gone" + android:layout_gravity="center_horizontal" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml new file mode 100644 index 00000000..d920c307 --- /dev/null +++ b/app/src/main/res/layout/activity_notification.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml new file mode 100644 index 00000000..10fc1bef --- /dev/null +++ b/app/src/main/res/layout/fragment_notifications.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file