This commit is contained in:
rebelonion 2024-05-27 05:11:48 -05:00
commit 0b32636c1b
25 changed files with 852 additions and 679 deletions

View file

@ -108,7 +108,7 @@ jobs:
#Telegram #Telegram
curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \ curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \
-F "document=@app/build/outputs/apk/google/alpha/app-google-universal-alpha.apk" \ -F "document=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \
-F "caption=Alpha-Build: ${VERSION}: ${commit_messages}" \ -F "caption=Alpha-Build: ${VERSION}: ${commit_messages}" \
https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument

View file

@ -194,7 +194,7 @@
android:label="Inbox Activity" android:label="Inbox Activity"
android:parentActivityName=".MainActivity" /> android:parentActivityName=".MainActivity" />
<activity <activity
android:name=".profile.activity.NotificationActivity" android:name=".profile.notification.NotificationActivity"
android:label="Inbox Activity" android:label="Inbox Activity"
android:parentActivityName=".MainActivity" /> android:parentActivityName=".MainActivity" />
<activity <activity

View file

@ -51,7 +51,7 @@ import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.others.calc.CalcActivity import ani.dantotsu.others.calc.CalcActivity
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.FeedActivity import ani.dantotsu.profile.activity.FeedActivity
import ani.dantotsu.profile.activity.NotificationActivity import ani.dantotsu.profile.notification.NotificationActivity
import ani.dantotsu.settings.ExtensionsActivity import ani.dantotsu.settings.ExtensionsActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefManager.asLiveBool import ani.dantotsu.settings.saving.PrefManager.asLiveBool
@ -365,7 +365,6 @@ class MainActivity : AppCompatActivity() {
} else if (fragmentToLoad == "NOTIFICATIONS" && activityId != -1) { } else if (fragmentToLoad == "NOTIFICATIONS" && activityId != -1) {
Logger.log("MainActivity, onCreate: $activityId") Logger.log("MainActivity, onCreate: $activityId")
val notificationIntent = Intent(this, NotificationActivity::class.java).apply { val notificationIntent = Intent(this, NotificationActivity::class.java).apply {
putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS")
putExtra("activityId", activityId) putExtra("activityId", activityId)
} }
launched = true launched = true

View file

@ -435,7 +435,7 @@ class AnilistQueries {
response.data.page2.activities response.data.page2.activities
).asSequence().flatten() ).asSequence().flatten()
.filter { it.typename != "MessageActivity" } .filter { it.typename != "MessageActivity" }
.filter { if (Anilist.adult) true else it.media?.isAdult == false } .filter { if (Anilist.adult) true else it.media?.isAdult != true }
.filter { it.createdAt * 1000L > threeDaysAgo }.toList() .filter { it.createdAt * 1000L > threeDaysAgo }.toList()
.sortedByDescending { it.createdAt } .sortedByDescending { it.createdAt }
val anilistActivities = mutableListOf<User>() val anilistActivities = mutableListOf<User>()

View file

@ -268,8 +268,9 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
LinearLayoutManager.HORIZONTAL, LinearLayoutManager.HORIZONTAL,
false false
) )
MediaListViewActivity.passedMedia = media.toCollection(ArrayList())
more.setOnClickListener { more.setOnClickListener {
MediaListViewActivity.passedMedia = media.toCollection(ArrayList())
ContextCompat.startActivity( ContextCompat.startActivity(
it.context, Intent(it.context, MediaListViewActivity::class.java) it.context, Intent(it.context, MediaListViewActivity::class.java)
.putExtra("title", string), .putExtra("title", string),

View file

@ -19,6 +19,7 @@ import ani.dantotsu.snackString
import ani.dantotsu.util.MarkdownCreatorActivity import ani.dantotsu.util.MarkdownCreatorActivity
import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -58,6 +59,11 @@ class RepliesBottomDialog : BottomSheetDialogFragment() {
activityId = requireArguments().getInt("activityId") activityId = requireArguments().getInt("activityId")
loading(true) loading(true)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
loadData()
}
}
private suspend fun loadData() {
val response = Anilist.query.getReplies(activityId) val response = Anilist.query.getReplies(activityId)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
loading(false) loading(false)
@ -81,8 +87,6 @@ class RepliesBottomDialog : BottomSheetDialogFragment() {
} }
} }
}
private fun onClick(int: Int) { private fun onClick(int: Int) {
ContextCompat.startActivity( ContextCompat.startActivity(
requireContext(), requireContext(),
@ -101,6 +105,15 @@ class RepliesBottomDialog : BottomSheetDialogFragment() {
super.onDestroyView() super.onDestroyView()
} }
override fun onResume() {
super.onResume()
loading(true)
lifecycleScope.launch(Dispatchers.IO) {
delay(2000)
loadData()
}
}
companion object { companion object {
fun newInstance(activityId: Int): RepliesBottomDialog { fun newInstance(activityId: Int): RepliesBottomDialog {
return RepliesBottomDialog().apply { return RepliesBottomDialog().apply {

View file

@ -74,8 +74,7 @@ class Stories @JvmOverloads constructor(
if (context is StoriesCallback) storiesListener = context as StoriesCallback if (context is StoriesCallback) storiesListener = context as StoriesCallback
binding.leftTouchPanel.setOnTouchListener(this) binding.touchPanel.setOnTouchListener(this)
binding.rightTouchPanel.setOnTouchListener(this)
} }
@ -264,49 +263,7 @@ class Stories @JvmOverloads constructor(
} }
private var startClickTime = 0L
private var startX = 0f
private var startY = 0f
private var isLongPress = false
private val swipeThreshold = 100
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
val maxClickDuration = 200
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
startX = event.x
startY = event.y
startClickTime = Calendar.getInstance().timeInMillis
pause()
isLongPress = false
}
MotionEvent.ACTION_MOVE -> {
val deltaX = event.x - startX
val deltaY = event.y - startY
if (!isLongPress && (abs(deltaX) > swipeThreshold || abs(deltaY) > swipeThreshold)) {
isLongPress = true
}
}
MotionEvent.ACTION_UP -> {
val clickDuration = Calendar.getInstance().timeInMillis - startClickTime
if (clickDuration < maxClickDuration && !isLongPress) {
when (view?.id) {
R.id.leftTouchPanel -> leftPanelTouch()
R.id.rightTouchPanel -> rightPanelTouch()
}
} else {
resume()
}
val deltaX = event.x - startX
if (abs(deltaX) > swipeThreshold) {
if (deltaX > 0) onStoriesPrevious()
else onStoriesCompleted()
}
}
}
return true
}
private fun rightPanelTouch() { private fun rightPanelTouch() {
Logger.log("rightPanelTouch: $storyIndex") Logger.log("rightPanelTouch: $storyIndex")
@ -359,6 +316,7 @@ class Stories @JvmOverloads constructor(
timer.resume() timer.resume()
} }
@SuppressLint("ClickableViewAccessibility")
private fun loadStory(story: Activity) { private fun loadStory(story: Activity) {
val key = "activities" val key = "activities"
val set = PrefManager.getCustomVal<Set<Int>>(key, setOf()).plus((story.id)) val set = PrefManager.getCustomVal<Set<Int>>(key, setOf()).plus((story.id))
@ -374,6 +332,15 @@ class Stories @JvmOverloads constructor(
null null
) )
} }
binding.textActivity.setOnTouchListener { v, event ->
onTouchView(v, event, true)
v.onTouchEvent(event)
}
binding.textActivityContainer.setOnTouchListener { v, event ->
onTouchView(v, event, true)
v.onTouchEvent(event)
}
fun visible(isList: Boolean) { fun visible(isList: Boolean) {
binding.textActivity.isVisible = !isList binding.textActivity.isVisible = !isList
binding.textActivityContainer.isVisible = !isList binding.textActivityContainer.isVisible = !isList
@ -502,4 +469,66 @@ class Stories @JvmOverloads constructor(
} }
} }
} }
private var startClickTime = 0L
private var startX = 0f
private var startY = 0f
private var isLongPress = false
private val swipeThreshold = 100
override fun onTouch(view: View, event: MotionEvent): Boolean {
onTouchView(view, event)
return true
}
private fun onTouchView(view: View, event: MotionEvent, isText: Boolean = false){
val maxClickDuration = 200
val screenWidth = view.width
val leftHalf = screenWidth / 2
val leftQuarter = screenWidth * 0.15
val rightQuarter = screenWidth * 0.85
when (event.action) {
MotionEvent.ACTION_DOWN -> {
startX = event.x
startY = event.y
startClickTime = Calendar.getInstance().timeInMillis
pause()
isLongPress = false
}
MotionEvent.ACTION_MOVE -> {
val deltaX = event.x - startX
val deltaY = event.y - startY
if (!isLongPress && (abs(deltaX) > swipeThreshold || abs(deltaY) > swipeThreshold)) {
isLongPress = true
}
}
MotionEvent.ACTION_UP -> {
val clickDuration = Calendar.getInstance().timeInMillis - startClickTime
if (isText) {
if (clickDuration < maxClickDuration && !isLongPress) {
if (event.x < leftQuarter) {
leftPanelTouch()
} else if (event.x > rightQuarter) {
rightPanelTouch()
}
} else {
resume()
}
} else {
if (clickDuration < maxClickDuration && !isLongPress) {
if (event.x < leftHalf) {
leftPanelTouch()
} else {
rightPanelTouch()
}
} else {
resume()
}
}
val deltaX = event.x - startX
if (abs(deltaX) > swipeThreshold) {
if (deltaX > 0) onStoriesPrevious()
else onStoriesCompleted()
}
}
}
}
} }

View file

@ -30,8 +30,8 @@ import ani.dantotsu.media.user.ListActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.openImage import ani.dantotsu.openImage
import ani.dantotsu.openLinkInBrowser import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.profile.activity.ActivityFragment
import ani.dantotsu.profile.activity.FeedFragment import ani.dantotsu.profile.activity.ActivityFragment.Companion.ActivityType
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
@ -156,6 +156,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
openLinkInBrowser("https://anilist.co/user/${user.name}") openLinkInBrowser("https://anilist.co/user/${user.name}")
true true
} }
R.id.action_create_new_activity -> { R.id.action_create_new_activity -> {
ContextCompat.startActivity( ContextCompat.startActivity(
context, context,
@ -165,6 +166,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
) )
true true
} }
else -> false else -> false
} }
} }
@ -177,7 +179,8 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
user.avatar?.medium ?: "" user.avatar?.medium ?: ""
) )
profileUserName.text = user.name profileUserName.text = user.name
val bannerAnimations: ImageView= if (PrefManager.getVal(PrefName.BannerAnimations)) profileBannerImage else profileBannerImageNoKen val bannerAnimations: ImageView =
if (PrefManager.getVal(PrefName.BannerAnimations)) profileBannerImage else profileBannerImageNoKen
blurImage( blurImage(
bannerAnimations, bannerAnimations,
@ -199,7 +202,8 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
profileAppBar.addOnOffsetChangedListener(context) profileAppBar.addOnOffsetChangedListener(context)
profileFollowerCount.text = (respond.data.followerPage?.pageInfo?.total ?: 0).toString() profileFollowerCount.text =
(respond.data.followerPage?.pageInfo?.total ?: 0).toString()
profileFollowerCountContainer.setOnClickListener { profileFollowerCountContainer.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
context, context,
@ -209,7 +213,8 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
null null
) )
} }
profileFollowingCount.text = (respond.data.followingPage?.pageInfo?.total ?: 0).toString() profileFollowingCount.text =
(respond.data.followingPage?.pageInfo?.total ?: 0).toString()
profileFollowingCountContainer.setOnClickListener { profileFollowingCountContainer.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
context, context,
@ -320,7 +325,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
override fun getItemCount(): Int = 3 override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment = when (position) { override fun createFragment(position: Int): Fragment = when (position) {
0 -> ProfileFragment.newInstance(user) 0 -> ProfileFragment.newInstance(user)
1 -> FeedFragment.newInstance(user.id, false, -1) 1 -> ActivityFragment(ActivityType.OTHER_USER, user.id)
2 -> StatsFragment.newInstance(user) 2 -> StatsFragment.newInstance(user)
else -> ProfileFragment.newInstance(user) else -> ProfileFragment.newInstance(user)
} }

View file

@ -0,0 +1,159 @@
package ani.dantotsu.profile.activity
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.core.view.updateLayoutParams
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.Activity
import ani.dantotsu.databinding.FragmentFeedBinding
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.setBaseline
import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.launch
class ActivityFragment(
var type: ActivityType,
val userId: Int? = null,
var activityId: Int? = null,
) : Fragment() {
private lateinit var binding: FragmentFeedBinding
private var adapter: GroupieAdapter = GroupieAdapter()
private var page: Int = 1
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFeedBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navBar = if (userId != null) {
(activity as ProfileActivity).navBar
} else {
(activity as FeedActivity).navBar
}
binding.listRecyclerView.setBaseline(navBar)
binding.listRecyclerView.adapter = adapter
binding.listRecyclerView.layoutManager = LinearLayoutManager(context)
binding.listProgressBar.isVisible = true
binding.feedRefresh.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
}
binding.emptyTextView.text = getString(R.string.no_notifications)
lifecycleScope.launch {
getList()
if (adapter.itemCount == 0) {
binding.emptyTextView.isVisible = true
}
binding.listProgressBar.isVisible = false
}
binding.feedSwipeRefresh.setOnRefreshListener {
lifecycleScope.launch {
adapter.clear()
page = 1
getList()
binding.feedSwipeRefresh.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 {
binding.feedRefresh.isVisible = true
getList()
binding.feedRefresh.isVisible = false
}
}
}
})
}
private suspend fun getList() {
val list = when (type) {
ActivityType.GLOBAL -> getActivities(true)
ActivityType.USER -> getActivities()
ActivityType.OTHER_USER -> getActivities(userId = userId)
ActivityType.ONE -> getActivities(activityId = activityId)
}
adapter.addAll(list.map { ActivityItem(it, ::onActivityClick, requireActivity()) })
}
private suspend fun getActivities(
global: Boolean = false,
userId: Int? = null,
activityId: Int? = null,
): List<Activity> {
val res = Anilist.query.getFeed(userId, global, page, activityId)?.data?.page?.activities
page += 1
return res
?.filter { if (Anilist.adult) true else it.media?.isAdult != true }
?.filterNot { it.recipient?.id != null && it.recipient.id != Anilist.userid }
?: emptyList()
}
private fun shouldLoadMore(): Boolean {
val layoutManager =
(binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
val adapter = binding.listRecyclerView.adapter
return !binding.listRecyclerView.canScrollVertically(1) &&
!binding.feedRefresh.isVisible && adapter?.itemCount != 0 &&
layoutManager == (adapter!!.itemCount - 1)
}
private fun onActivityClick(id: Int, type: String) {
when (type) {
"USER" -> {
ContextCompat.startActivity(
requireContext(), Intent(requireContext(), ProfileActivity::class.java)
.putExtra("userId", id), null
)
}
"MEDIA" -> {
ContextCompat.startActivity(
requireContext(), Intent(requireContext(), MediaDetailsActivity::class.java)
.putExtra("mediaId", id), null
)
}
}
}
override fun onResume() {
super.onResume()
if (this::binding.isInitialized) {
binding.root.requestLayout()
val navBar = if (userId != null) {
(activity as ProfileActivity).navBar
} else {
(activity as FeedActivity).navBar
}
binding.listRecyclerView.setBaseline(navBar)
}
}
companion object {
enum class ActivityType {
GLOBAL, USER, OTHER_USER, ONE
}
}
}

View file

@ -2,6 +2,7 @@ package ani.dantotsu.profile.activity
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
@ -10,16 +11,20 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.databinding.ActivityFeedBinding import ani.dantotsu.databinding.ActivityFeedBinding
import ani.dantotsu.databinding.ActivityNotificationBinding
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.profile.activity.ActivityFragment.Companion.ActivityType
import ani.dantotsu.profile.notification.NotificationActivity
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
class FeedActivity : AppCompatActivity() { class FeedActivity : AppCompatActivity() {
private lateinit var binding: ActivityFeedBinding private lateinit var binding: ActivityNotificationBinding
private var selected: Int = 0 private var selected: Int = 0
lateinit var navBar: AnimatedBottomBar lateinit var navBar: AnimatedBottomBar
@ -27,28 +32,27 @@ class FeedActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
initActivity(this) initActivity(this)
binding = ActivityFeedBinding.inflate(layoutInflater) binding = ActivityNotificationBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
navBar = binding.feedNavBar binding.notificationTitle.text = getString(R.string.activities)
val navBarMargin = if (resources.configuration.orientation == binding.notificationToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
Configuration.ORIENTATION_LANDSCAPE topMargin = statusBarHeight
) 0 else navBarHeight
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarMargin }
val personalTab = navBar.createTab(R.drawable.ic_round_person_24, "Following")
val globalTab = navBar.createTab(R.drawable.ic_globe_24, "Global")
navBar.addTab(personalTab)
navBar.addTab(globalTab)
binding.listTitle.text = getString(R.string.activities)
binding.feedViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarMargin
topMargin += statusBarHeight
} }
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight } navBar = binding.notificationNavBar
val activityId = intent.getIntExtra("activityId", -1) binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
binding.feedViewPager.adapter = bottomMargin = navBarHeight
ViewPagerAdapter(supportFragmentManager, lifecycle, activityId) }
binding.feedViewPager.setCurrentItem(selected, false) val tabs = listOf(
binding.feedViewPager.isUserInputEnabled = false Pair(R.drawable.ic_round_person_24, "Following"),
Pair(R.drawable.ic_globe_24, "Global"),
)
tabs.forEach { (icon, title) -> navBar.addTab(navBar.createTab(icon, title)) }
binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
val getOne = intent.getIntExtra("activityId", -1)
if (getOne != -1) { navBar.visibility = View.GONE }
binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne)
binding.notificationViewPager.setOffscreenPageLimit(4)
binding.notificationViewPager.setCurrentItem(selected, false)
navBar.selectTabAt(selected) navBar.selectTabAt(selected)
navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
override fun onTabSelected( override fun onTabSelected(
@ -58,24 +62,15 @@ class FeedActivity : AppCompatActivity() {
newTab: AnimatedBottomBar.Tab newTab: AnimatedBottomBar.Tab
) { ) {
selected = newIndex selected = newIndex
binding.feedViewPager.setCurrentItem(selected, true) binding.notificationViewPager.setCurrentItem(selected, true)
} }
}) })
binding.listBack.setOnClickListener { binding.notificationViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
onBackPressedDispatcher.onBackPressed() override fun onPageSelected(position: Int) {
super.onPageSelected(position)
navBar.selectTabAt(position)
} }
} })
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val margin =
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
val params: ViewGroup.MarginLayoutParams =
binding.feedViewPager.layoutParams as ViewGroup.MarginLayoutParams
val paramsNav: ViewGroup.MarginLayoutParams =
navBar.layoutParams as ViewGroup.MarginLayoutParams
params.updateMargins(bottom = margin)
paramsNav.updateMargins(bottom = margin)
} }
override fun onResume() { override fun onResume() {
@ -88,12 +83,12 @@ class FeedActivity : AppCompatActivity() {
lifecycle: Lifecycle, lifecycle: Lifecycle,
private val activityId: Int private val activityId: Int
) : FragmentStateAdapter(fragmentManager, lifecycle) { ) : FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int = 2 override fun getItemCount(): Int = if (activityId != -1) 1 else 2
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return when (position) { return when (position) {
0 -> FeedFragment.newInstance(null, false, activityId) 0 -> ActivityFragment(if (activityId != -1) ActivityType.ONE else ActivityType.USER, activityId = activityId)
else -> FeedFragment.newInstance(null, true, -1) else -> ActivityFragment(ActivityType.GLOBAL)
} }
} }
} }

View file

@ -1,188 +0,0 @@
package ani.dantotsu.profile.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
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 ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistQueries
import ani.dantotsu.connections.anilist.api.Activity
import ani.dantotsu.databinding.FragmentFeedBinding
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.setBaseline
import ani.dantotsu.util.Logger
import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class FeedFragment : Fragment() {
private lateinit var binding: FragmentFeedBinding
private var adapter: GroupieAdapter = GroupieAdapter()
private var activityList: List<Activity> = emptyList()
private lateinit var activity: androidx.activity.ComponentActivity
private var page: Int = 1
private var loadedFirstTime = false
private var userId: Int? = null
private var global: Boolean = false
private var activityId: Int = -1
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFeedBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity = requireActivity()
userId = arguments?.getInt("userId", -1)
activityId = arguments?.getInt("activityId", -1) ?: -1
if (userId == -1) userId = null
global = arguments?.getBoolean("global", false) ?: false
val navBar = if (userId != null) {
(activity as ProfileActivity).navBar
} else {
(activity as FeedActivity).navBar
}
binding.listRecyclerView.setBaseline(navBar)
binding.listRecyclerView.adapter = adapter
binding.listRecyclerView.layoutManager =
LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.listProgressBar.visibility = ViewGroup.VISIBLE
}
@SuppressLint("ClickableViewAccessibility")
override fun onResume() {
super.onResume()
if (this::binding.isInitialized) {
binding.root.requestLayout()
val navBar = if (userId != null) {
(activity as ProfileActivity).navBar
} else {
(activity as FeedActivity).navBar
}
binding.listRecyclerView.setBaseline(navBar)
if (!loadedFirstTime) {
activity.lifecycleScope.launch(Dispatchers.IO) {
val nulledId = if (activityId == -1) null else activityId
val res = Anilist.query.getFeed(userId, global, activityId = nulledId)
withContext(Dispatchers.Main) {
res?.data?.page?.activities?.let { activities ->
activityList = activities
val filtered =
activityList
.filter { if (Anilist.adult) true else it.media?.isAdult == false }
.filterNot { //filter out messages that are not directed to the user
it.recipient?.id != null && it.recipient.id != Anilist.userid
}
adapter.update(filtered.map {
ActivityItem(
it,
::onActivityClick,
requireActivity()
)
})
}
binding.listProgressBar.visibility = ViewGroup.GONE
val scrollView = binding.listRecyclerView
binding.listRecyclerView.setOnTouchListener { _, event ->
if (event?.action == MotionEvent.ACTION_UP) {
if (activityList.size % AnilistQueries.ITEMS_PER_PAGE != 0 && !global) {
//snackString("No more activities") fix spam?
Logger.log("No more activities")
} else if (!scrollView.canScrollVertically(1) && !binding.feedRefresh.isVisible
&& binding.listRecyclerView.adapter!!.itemCount != 0 &&
(binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1)
) {
page++
binding.feedRefresh.visibility = ViewGroup.VISIBLE
loadPage {
binding.feedRefresh.visibility = ViewGroup.GONE
}
}
}
false
}
binding.feedSwipeRefresh.setOnRefreshListener {
page = 1
adapter.clear()
activityList = emptyList()
loadPage()
}
}
}
loadedFirstTime = true
}
}
}
private fun loadPage(onFinish: () -> Unit = {}) {
activity.lifecycleScope.launch(Dispatchers.IO) {
val newRes = Anilist.query.getFeed(userId, global, page)
withContext(Dispatchers.Main) {
newRes?.data?.page?.activities?.let { activities ->
activityList += activities
val filtered = activities.filterNot {
it.recipient?.id != null && it.recipient.id != Anilist.userid
}
adapter.addAll(filtered.map {
ActivityItem(
it,
::onActivityClick,
requireActivity()
)
})
}
binding.feedSwipeRefresh.isRefreshing = false
onFinish()
}
}
}
private fun onActivityClick(id: Int, type: String) {
when (type) {
"USER" -> {
ContextCompat.startActivity(
activity, Intent(activity, ProfileActivity::class.java)
.putExtra("userId", id), null
)
}
"MEDIA" -> {
ContextCompat.startActivity(
activity, Intent(activity, MediaDetailsActivity::class.java)
.putExtra("mediaId", id), null
)
}
}
}
companion object {
fun newInstance(userId: Int?, global: Boolean, activityId: Int): FeedFragment {
val fragment = FeedFragment()
val args = Bundle()
args.putInt("userId", userId ?: -1)
args.putBoolean("global", global)
args.putInt("activityId", activityId)
fragment.arguments = args
return fragment
}
}
}

View file

@ -1,309 +0,0 @@
package ani.dantotsu.profile.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Notification
import ani.dantotsu.connections.anilist.api.NotificationType
import ani.dantotsu.connections.anilist.api.NotificationType.Companion.fromFormattedString
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ActivityFollowBinding
import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.comment.CommentStore
import ani.dantotsu.notifications.subscription.SubscriptionStore
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.util.Logger
import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class NotificationActivity : AppCompatActivity() {
private lateinit var binding: ActivityFollowBinding
private lateinit var commentStore: List<CommentStore>
private lateinit var subscriptionStore: List<SubscriptionStore>
private var adapter: GroupieAdapter = GroupieAdapter()
private var notificationList: List<Notification> = emptyList()
val filters = ArrayList<String>()
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<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
}
binding.listFrameLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
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<List<CommentStore>>(
PrefName.CommentNotificationStore,
null
) ?: listOf()
subscriptionStore = PrefManager.getNullableVal<List<SubscriptionStore>>(
PrefName.SubscriptionNotificationStore,
null
) ?: listOf()
binding.followFilterButton.setOnClickListener {
val dialogView = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
val checkboxContainer = dialogView.findViewById<LinearLayout>(R.id.checkboxContainer)
val tickAllButton = dialogView.findViewById<ImageButton>(R.id.toggleButton)
val title = dialogView.findViewById<TextView>(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<String>(PrefName.AnilistUserId).toIntOrNull()
?: 0, currentPage, resetNotification = resetNotification
)
withContext(Dispatchers.Main) {
val newNotifications: MutableList<Notification> = 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
}
}
}

View file

@ -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.isVisible
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 ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType
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<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
}
navBar = binding.notificationNavBar
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
}
val tabs = listOf(
Pair(R.drawable.ic_round_movie_filter_24, "Media"),
Pair(R.drawable.ic_round_person_24, "User"),
Pair(R.drawable.ic_round_notifications_active_24, "Subs"),
Pair(R.drawable.ic_round_comment_24, "Comments")
)
tabs.forEach { (icon, title) -> navBar.addTab(navBar.createTab(icon, title)) }
binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
val getOne = intent.getIntExtra("activityId", -1)
if (getOne != -1) navBar.isVisible = false
binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne)
binding.notificationViewPager.setOffscreenPageLimit(4)
binding.notificationViewPager.setCurrentItem(selected, false)
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() {
super.onResume()
if (this::navBar.isInitialized) {
navBar.selectTabAt(selected)
}
}
private class ViewPagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
val id: Int = -1
) : FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int = if (id != -1) 1 else 4
override fun createFragment(position: Int): Fragment = when (position) {
0 -> NotificationFragment(if (id != -1) NotificationType.ONE else NotificationType.MEDIA, id)
1 -> NotificationFragment(NotificationType.USER)
2 -> NotificationFragment(NotificationType.SUBSCRIPTION)
3 -> NotificationFragment(NotificationType.COMMENT)
else -> NotificationFragment(NotificationType.MEDIA)
}
}
}

View file

@ -0,0 +1,228 @@
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.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.webkit.internal.ApiFeature.N
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.navBarHeight
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.launch
class NotificationFragment(
val type: NotificationType,
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.notificationRecyclerView.setBaseline(navbar)
binding.notificationRecyclerView.adapter = adapter
binding.notificationRecyclerView.layoutManager = LinearLayoutManager(context)
binding.notificationProgressBar.isVisible = true
binding.notificationRefresh.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
}
binding.emptyTextView.text = getString(R.string.no_notifications)
lifecycleScope.launch {
getList()
if (adapter.itemCount == 0) {
binding.emptyTextView.isVisible = true
}
binding.notificationProgressBar.isVisible = false
}
binding.notificationSwipeRefresh.setOnRefreshListener {
lifecycleScope.launch {
adapter.clear()
currentPage = 1
getList()
binding.notificationSwipeRefresh.isRefreshing = false
}
}
binding.notificationRecyclerView.addOnScrollListener(object :
RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (shouldLoadMore()) {
lifecycleScope.launch {
binding.notificationRefresh.isVisible = true
getList()
binding.notificationRefresh.isVisible = false
}
}
}
})
}
private suspend fun getList() {
val list = when (type) {
NotificationType.ONE -> getNotificationsFiltered(false) { it.id == getID }
NotificationType.MEDIA -> getNotificationsFiltered { it.media != null }
NotificationType.USER -> getNotificationsFiltered { it.media == null }
NotificationType.SUBSCRIPTION -> getSubscriptions()
NotificationType.COMMENT -> getComments()
}
adapter.addAll(list.map { NotificationItem(it, ::onClick) })
}
private suspend fun getNotificationsFiltered(
reset: Boolean = true,
filter: (Notification) -> Boolean
): List<Notification> {
val userId =
Anilist.userid ?: PrefManager.getVal<String>(PrefName.AnilistUserId).toIntOrNull() ?: 0
val res = Anilist.query.getNotifications(userId, currentPage, reset)?.data?.page
currentPage = res?.pageInfo?.currentPage?.plus(1) ?: 1
hasNextPage = res?.pageInfo?.hasNextPage ?: false
return res?.notifications?.filter(filter) ?: listOf()
}
private fun getSubscriptions(): List<Notification> {
val list = PrefManager.getNullableVal<List<SubscriptionStore>>(
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<Notification> {
val list = PrefManager.getNullableVal<List<CommentStore>>(
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.notificationRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
val adapter = binding.notificationRecyclerView.adapter
return hasNextPage && !binding.notificationRefresh.isVisible && adapter?.itemCount != 0 &&
layoutManager == (adapter!!.itemCount - 1) &&
!binding.notificationRecyclerView.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
}
enum class NotificationType {
MEDIA, USER, SUBSCRIPTION, COMMENT, ONE
}
}
}

View file

@ -1,4 +1,4 @@
package ani.dantotsu.profile.activity package ani.dantotsu.profile.notification
import android.view.View import android.view.View
import android.view.ViewGroup 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.connections.anilist.api.NotificationType
import ani.dantotsu.databinding.ItemNotificationBinding import ani.dantotsu.databinding.ItemNotificationBinding
import ani.dantotsu.loadImage 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.setAnimation
import ani.dantotsu.toPx import ani.dantotsu.toPx
import com.xwray.groupie.viewbinding.BindableItem import com.xwray.groupie.viewbinding.BindableItem

View file

@ -1,6 +1,5 @@
package ani.dantotsu.settings package ani.dantotsu.settings
import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
@ -16,7 +15,6 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.BottomSheetSettingsBinding import ani.dantotsu.databinding.BottomSheetSettingsBinding
import ani.dantotsu.download.anime.OfflineAnimeFragment import ani.dantotsu.download.anime.OfflineAnimeFragment
import ani.dantotsu.download.manga.OfflineMangaFragment import ani.dantotsu.download.manga.OfflineMangaFragment
import ani.dantotsu.getAppString
import ani.dantotsu.getThemeColor import ani.dantotsu.getThemeColor
import ani.dantotsu.home.AnimeFragment import ani.dantotsu.home.AnimeFragment
import ani.dantotsu.home.HomeFragment import ani.dantotsu.home.HomeFragment
@ -28,7 +26,7 @@ import ani.dantotsu.loadImage
import ani.dantotsu.offline.OfflineFragment import ani.dantotsu.offline.OfflineFragment
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.FeedActivity 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.setSafeOnClickListener
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName

View file

@ -135,6 +135,7 @@ class AlertDialogBuilder(private val context: Context) {
} else if (checkedItems != null && onItemsSelected != null) { } else if (checkedItems != null && onItemsSelected != null) {
builder.setMultiChoiceItems(items, checkedItems) { _, which, isChecked -> builder.setMultiChoiceItems(items, checkedItems) { _, which, isChecked ->
checkedItems?.set(which, isChecked) checkedItems?.set(which, isChecked)
onItemsSelected?.invoke(checkedItems!!)
} }
} }
} }

View file

@ -56,6 +56,7 @@
app:abb_animationInterpolator="@anim/over_shoot" app:abb_animationInterpolator="@anim/over_shoot"
app:abb_indicatorAppearance="round" app:abb_indicatorAppearance="round"
app:abb_indicatorLocation="top" app:abb_indicatorLocation="top"
app:abb_indicatorMargin="28dp"
app:abb_selectedTabType="text" app:abb_selectedTabType="text"
app:abb_textAppearance="@style/NavBarText" app:abb_textAppearance="@style/NavBarText"
app:itemActiveIndicatorStyle="@style/BottomNavBar" app:itemActiveIndicatorStyle="@style/BottomNavBar"

View file

@ -103,15 +103,18 @@
</FrameLayout> </FrameLayout>
<FrameLayout <LinearLayout
android:id="@+id/listFrameLayout" android:id="@+id/listFrameLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/followSwipeRefresh" android:id="@+id/followSwipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:clipChildren="false" android:clipChildren="false"
@ -120,7 +123,7 @@
<ani.dantotsu.FadingEdgeRecyclerView <ani.dantotsu.FadingEdgeRecyclerView
android:id="@+id/listRecyclerView" android:id="@+id/listRecyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:nestedScrollingEnabled="true" android:nestedScrollingEnabled="true"
android:requiresFadingEdge="vertical" android:requiresFadingEdge="vertical"
tools:listitem="@layout/item_follower" /> tools:listitem="@layout/item_follower" />
@ -132,8 +135,8 @@
style="?android:attr/progressBarStyle" style="?android:attr/progressBarStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"
android:visibility="gone" /> android:visibility="gone"
</FrameLayout> android:layout_gravity="center_horizontal" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/notificationToolbar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/notificationBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="12dp"
android:src="@drawable/ic_round_arrow_back_ios_new_24"
app:tint="?attr/colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/notificationTitle"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:ellipsize="end"
android:fontFamily="@font/poppins_bold"
android:gravity="center|start"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:textColor="?attr/colorOnBackground"
android:textSize="18sp"
tools:text="Notifications" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/notificationViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:nestedScrollingEnabled="true"
tools:ignore="SpeakableTextPresentCheck" />
</LinearLayout>
<nl.joery.animatedbottombar.AnimatedBottomBar
android:id="@+id/notificationNavBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginTop="-64dp"
android:background="?attr/colorSurface"
android:padding="0dp"
app:abb_animationInterpolator="@anim/over_shoot"
app:abb_indicatorAppearance="round"
app:abb_indicatorLocation="top"
app:abb_indicatorMargin="28dp"
app:abb_selectedTabType="text"
app:abb_textAppearance="@style/NavBarText"
app:itemActiveIndicatorStyle="@style/BottomNavBar"
app:itemIconTint="@color/tab_layout_icon"
app:itemRippleColor="#00000000"
app:itemTextAppearanceActive="@style/NavBarText"
app:itemTextAppearanceInactive="@style/NavBarText"
app:itemTextColor="@color/tab_layout_icon" />
</LinearLayout>

View file

@ -85,7 +85,7 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:layout_width="52dp" android:layout_width="52dp"
android:layout_height="52dp" android:layout_height="52dp"
app:strokeColor="@color/transparent"
android:backgroundTint="@color/nav_bg_inv" android:backgroundTint="@color/nav_bg_inv"
app:cardCornerRadius="26dp"> app:cardCornerRadius="26dp">

View file

@ -4,50 +4,62 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<ProgressBar <LinearLayout
android:id="@+id/listProgressBar" android:id="@+id/listProgressBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:visibility="gone">
<ProgressBar
style="?android:attr/progressBarStyle" style="?android:attr/progressBarStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:layout_gravity="center" </LinearLayout>
android:visibility="gone" />
<FrameLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/emptyTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/poppins_semi_bold"
android:gravity="center"
android:visibility="gone" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/feedSwipeRefresh" android:id="@+id/feedSwipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:layout_weight="1"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"> android:clipToPadding="false">
<ani.dantotsu.FadingEdgeRecyclerView <ani.dantotsu.FadingEdgeRecyclerView
android:id="@+id/listRecyclerView" android:id="@+id/listRecyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:nestedScrollingEnabled="true" android:nestedScrollingEnabled="true"
android:requiresFadingEdge="vertical" android:requiresFadingEdge="vertical"
android:visibility="visible"
tools:listitem="@layout/item_activity" /> tools:listitem="@layout/item_activity" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<ProgressBar <ProgressBar
android:id="@+id/feedRefresh" android:id="@+id/feedRefresh"
style="?android:attr/progressBarStyle" style="?android:attr/progressBarStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"
android:visibility="gone" /> android:visibility="gone" />
</FrameLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/notificationProgressBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:visibility="gone">
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/emptyTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/poppins_semi_bold"
android:gravity="center"
android:visibility="gone" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/notificationSwipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false">
<ani.dantotsu.FadingEdgeRecyclerView
android:id="@+id/notificationRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:nestedScrollingEnabled="true"
android:requiresFadingEdge="vertical"
tools:listitem="@layout/item_follower" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<ProgressBar
android:id="@+id/notificationRefresh"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="32dp"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>

View file

@ -28,45 +28,36 @@
android:src="@drawable/linear_gradient_bg" android:src="@drawable/linear_gradient_bg"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<FrameLayout
android:id="@+id/touchPanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/textActivityContainer" android:id="@+id/textActivityContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:layout_marginHorizontal="16dp"
android:orientation="horizontal"> android:layout_marginVertical="94dp"
android:clipToPadding="false"
android:scrollbars="none">
<TextView <TextView
android:id="@+id/textActivity" android:id="@+id/textActivity"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold" android:fontFamily="@font/poppins_semi_bold"
android:gravity="center"
android:paddingHorizontal="12dp" android:paddingHorizontal="12dp"
android:text="Play" android:text="test"
android:textAlignment="center"
android:textColor="@color/bg_white" android:textColor="@color/bg_white"
android:textSize="18sp" android:textSize="18sp"
android:visibility="gone"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<FrameLayout
android:id="@+id/leftTouchPanel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.5" />
<FrameLayout
android:id="@+id/rightTouchPanel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.5" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -90,14 +90,14 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:backgroundTint="?attr/colorSecondaryContainer" android:backgroundTint="?attr/colorOnPrimary"
android:enabled="true" android:enabled="true"
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:text="@string/follow" android:text="@string/follow"
android:textColor="@color/bg_opp" android:textColor="@color/bg_opp"
android:textSize="14sp" android:textSize="14sp"
app:cornerRadius="8dp" app:cornerRadius="8dp"
app:strokeColor="?attr/colorSecondaryContainer" app:strokeColor="?attr/colorOnPrimary"
tools:ignore="SpeakableTextPresentCheck" /> tools:ignore="SpeakableTextPresentCheck" />
</LinearLayout> </LinearLayout>