diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 928f767..a0f90b7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -78,9 +78,8 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
implementation("androidx.preference:preference:1.1.1")
implementation("androidx.emoji:emoji-bundled:1.1.0")
- implementation("androidx.paging:paging-runtime-ktx:3.0.0-alpha05")
+ implementation("androidx.paging:paging-runtime-ktx:3.0.0-alpha06")
implementation("androidx.viewpager2:viewpager2:1.0.0")
- implementation("androidx.window:window:1.0.0-alpha01")
implementation("androidx.room:room-ktx:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/compose/ComposeActivity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/compose/ComposeActivity.kt
index bd7180e..82f83d0 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/components/compose/ComposeActivity.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/compose/ComposeActivity.kt
@@ -25,7 +25,6 @@ import android.content.Intent
import android.os.Bundle
import android.view.ViewGroup
import androidx.activity.viewModels
-import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt
index bc911d3..0aba3af 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt
@@ -23,7 +23,6 @@ import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.ViewGroup
-import android.widget.LinearLayout
import androidx.activity.viewModels
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationAdapter.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationAdapter.kt
new file mode 100644
index 0000000..ed2fb4a
--- /dev/null
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationAdapter.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 Conny Duck
+ *
+ * This file is part of Pixelcat.
+ *
+ * Pixelcat is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pixelcat is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.connyduck.pixelcat.components.notifications
+
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.text.parseAsHtml
+import androidx.paging.PagingDataAdapter
+import androidx.recyclerview.widget.DiffUtil
+import at.connyduck.pixelcat.R
+import at.connyduck.pixelcat.components.util.BindingHolder
+import at.connyduck.pixelcat.components.util.extension.hide
+import at.connyduck.pixelcat.databinding.ItemNotificationBinding
+import at.connyduck.pixelcat.databinding.ItemNotificationFollowBinding
+import at.connyduck.pixelcat.databinding.ItemReplyBinding
+import at.connyduck.pixelcat.db.entitity.NotificationEntity
+import at.connyduck.pixelcat.db.entitity.StatusEntity
+import at.connyduck.pixelcat.db.entitity.TimelineAccountEntity
+import at.connyduck.pixelcat.model.Notification
+import coil.load
+import coil.transform.RoundedCornersTransformation
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+
+interface NotificationActionListener {
+ fun onDetailsOpened(status: StatusEntity)
+ fun onProfileOpened(account: TimelineAccountEntity)
+}
+
+object NotificationDiffUtil : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: NotificationEntity, newItem: NotificationEntity): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: NotificationEntity, newItem: NotificationEntity): Boolean {
+ return oldItem == newItem
+ }
+}
+
+class NotificationAdapter(
+ private val listener: NotificationActionListener
+) : PagingDataAdapter>(NotificationDiffUtil) {
+
+ private val dateTimeFormatter = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<*> {
+ val binding = when (viewType) {
+ MENTION -> ItemReplyBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ FOLLOW -> ItemNotificationFollowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ FAVOURITE -> ItemNotificationBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ REBLOG -> ItemNotificationBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ else -> throw IllegalStateException()
+ }
+ return BindingHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: BindingHolder<*>, position: Int) {
+ getItem(position)?.let { notification ->
+ when (holder.binding) {
+ is ItemReplyBinding -> holder.binding.bind(notification, dateTimeFormatter)
+ is ItemNotificationFollowBinding -> holder.binding.bind(notification, listener)
+ is ItemNotificationBinding -> holder.binding.bind(notification, listener)
+ }
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ Log.d("NotificationAdapter", "$position ${getItem(position)}")
+ return when (getItem(position)?.type) {
+ Notification.Type.MENTION -> MENTION
+ Notification.Type.REBLOG -> REBLOG
+ Notification.Type.FAVOURITE -> FAVOURITE
+ Notification.Type.FOLLOW -> FOLLOW
+ else -> throw IllegalStateException()
+ }
+ }
+
+ companion object {
+ private const val MENTION = 1
+ private const val FOLLOW = 2
+ private const val FAVOURITE = 3
+ private const val REBLOG = 4
+ }
+}
+
+private fun ItemReplyBinding.bind(notification: NotificationEntity, dateTimeFormatter: DateFormat) {
+ val status = notification.status!!
+
+ postAvatar.load(status.account.avatar) {
+ transformations(RoundedCornersTransformation(25f))
+ }
+
+ postDisplayName.text = status.account.displayName
+ postName.text = "@${status.account.username}"
+
+ postDescription.text = status.content.parseAsHtml().trim()
+
+ postDate.text = dateTimeFormatter.format(status.createdAt)
+
+ postLikeButton.hide()
+
+ postReplyButton.hide()
+}
+
+private fun ItemNotificationFollowBinding.bind(notification: NotificationEntity, listener: NotificationActionListener) {
+
+ val account = notification.account
+
+ notificationText.text = notificationText.context.getString(R.string.notification_followed, notification.account.username)
+
+ notificationAvatar.load(account.avatar) {
+ transformations(RoundedCornersTransformation(25f))
+ }
+ notificationDisplayName.text = account.displayName
+ notificationName.text = account.username
+
+ root.setOnClickListener {
+ listener.onProfileOpened(account)
+ }
+}
+
+private fun ItemNotificationBinding.bind(notification: NotificationEntity, listener: NotificationActionListener) {
+
+ notificationAvatar.load(notification.account.avatar) {
+ transformations(RoundedCornersTransformation(25f))
+ }
+
+ if (notification.type == Notification.Type.REBLOG) {
+ notificationText.text = notificationText.context.getString(R.string.notification_reblogged, notification.account.username)
+ } else { // Notification.Type.FAVOURITE
+ notificationText.text = notificationText.context.getString(R.string.notification_favourited, notification.account.username)
+ }
+
+ root.setOnClickListener {
+ listener.onDetailsOpened(notification.status!!)
+ }
+}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsFragment.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsFragment.kt
index e3f7f6a..f66a1bb 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsFragment.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsFragment.kt
@@ -19,18 +19,77 @@
package at.connyduck.pixelcat.components.notifications
+import android.os.Bundle
+import android.view.View
import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.LoadState
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.SimpleItemAnimator
import at.connyduck.pixelcat.R
+import at.connyduck.pixelcat.components.profile.ProfileActivity
+import at.connyduck.pixelcat.components.timeline.detail.DetailActivity
+import at.connyduck.pixelcat.components.util.getColorForAttr
import at.connyduck.pixelcat.dagger.ViewModelFactory
+import at.connyduck.pixelcat.databinding.FragmentNotificationsBinding
+import at.connyduck.pixelcat.db.entitity.StatusEntity
+import at.connyduck.pixelcat.db.entitity.TimelineAccountEntity
+import at.connyduck.pixelcat.util.viewBinding
import dagger.android.support.DaggerFragment
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
import javax.inject.Inject
-class NotificationsFragment : DaggerFragment(R.layout.fragment_notifications) {
+class NotificationsFragment :
+ DaggerFragment(R.layout.fragment_notifications),
+ NotificationActionListener {
@Inject
lateinit var viewModelFactory: ViewModelFactory
- private val notificationsViewModel: NotificationsViewModel by viewModels { viewModelFactory }
+ private val viewModel: NotificationsViewModel by viewModels { viewModelFactory }
+
+ private val binding by viewBinding(FragmentNotificationsBinding::bind)
+
+ @ExperimentalPagingApi
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+
+ binding.notificationSwipeRefresh.setColorSchemeColors(
+ view.context.getColorForAttr(R.attr.pixelcat_gradient_color_start),
+ view.context.getColorForAttr(R.attr.pixelcat_gradient_color_end)
+ )
+
+ val adapter = NotificationAdapter(this)
+
+ binding.notificationRecyclerView.adapter = adapter
+ (binding.notificationRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
+ binding.notificationRecyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
+
+ lifecycleScope.launch {
+ viewModel.notificationsFlow.collectLatest { pagingData ->
+ adapter.submitData(pagingData)
+ }
+ }
+
+ binding.notificationSwipeRefresh.setOnRefreshListener {
+ adapter.refresh()
+ }
+
+ adapter.addLoadStateListener {
+ if (it.refresh != LoadState.Loading) {
+ binding.notificationSwipeRefresh.isRefreshing = false
+ }
+ }
+ }
+
+ override fun onDetailsOpened(status: StatusEntity) {
+ startActivity(DetailActivity.newIntent(requireContext(), status.id))
+ }
+
+ override fun onProfileOpened(account: TimelineAccountEntity) {
+ startActivity(ProfileActivity.newIntent(requireContext(), account.id))
+ }
companion object {
fun newInstance() = NotificationsFragment()
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsRemoteMediator.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsRemoteMediator.kt
new file mode 100644
index 0000000..39ac2f9
--- /dev/null
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsRemoteMediator.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 Conny Duck
+ *
+ * This file is part of Pixelcat.
+ *
+ * Pixelcat is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pixelcat is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.connyduck.pixelcat.components.notifications
+
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.LoadType
+import androidx.paging.PagingState
+import androidx.paging.RemoteMediator
+import androidx.room.withTransaction
+import at.connyduck.pixelcat.db.AppDatabase
+import at.connyduck.pixelcat.db.entitity.NotificationEntity
+import at.connyduck.pixelcat.db.entitity.toEntity
+import at.connyduck.pixelcat.network.FediverseApi
+
+@ExperimentalPagingApi
+class NotificationsRemoteMediator(
+ private val accountId: Long,
+ private val api: FediverseApi,
+ private val db: AppDatabase
+) : RemoteMediator() {
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState
+ ): MediatorResult {
+ val apiCall = when (loadType) {
+ LoadType.REFRESH -> {
+ api.notifications(limit = state.config.initialLoadSize, excludes = setOf("poll", "follow_request"))
+ }
+ LoadType.PREPEND -> {
+ return MediatorResult.Success(true)
+ }
+ LoadType.APPEND -> {
+ val maxId = state.pages.findLast { it.data.isNotEmpty() }?.data?.lastOrNull()?.id
+ api.notifications(maxId = maxId, limit = state.config.pageSize, excludes = setOf("poll", "follow_request"))
+ }
+ }
+
+ return apiCall.fold(
+ { notificationResult ->
+ db.withTransaction {
+ if (loadType == LoadType.REFRESH) {
+ db.notificationsDao().clearAll(accountId)
+ }
+ db.notificationsDao().insertOrReplace(notificationResult.map { it.toEntity(accountId) })
+ }
+ MediatorResult.Success(endOfPaginationReached = notificationResult.isEmpty())
+ },
+ {
+ MediatorResult.Error(it)
+ }
+ )
+ }
+
+ override suspend fun initialize() = InitializeAction.SKIP_INITIAL_REFRESH
+}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt
index a33f5de..b0dbbef 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt
@@ -20,6 +20,35 @@
package at.connyduck.pixelcat.components.notifications
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.Pager
+import androidx.paging.PagingConfig
+import androidx.paging.cachedIn
+import at.connyduck.pixelcat.db.AccountManager
+import at.connyduck.pixelcat.db.AppDatabase
+import at.connyduck.pixelcat.network.FediverseApi
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.flatMapConcat
import javax.inject.Inject
-class NotificationsViewModel @Inject constructor() : ViewModel()
+class NotificationsViewModel @Inject constructor(
+ accountManager: AccountManager,
+ private val db: AppDatabase,
+ private val fediverseApi: FediverseApi
+) : ViewModel() {
+
+ @OptIn(FlowPreview::class)
+ @ExperimentalPagingApi
+ val notificationsFlow = accountManager::activeAccount.asFlow()
+ .flatMapConcat { activeAccount ->
+ Pager(
+ config = PagingConfig(pageSize = 10, enablePlaceholders = false),
+ remoteMediator = NotificationsRemoteMediator(activeAccount?.id!!, fediverseApi, db),
+ pagingSourceFactory = { db.notificationsDao().notifications(activeAccount.id) }
+ ).flow
+ }
+ .cachedIn(viewModelScope)
+
+}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/profile/ProfileActivity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/profile/ProfileActivity.kt
index 57c0cf3..1762856 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/components/profile/ProfileActivity.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/profile/ProfileActivity.kt
@@ -22,7 +22,6 @@ package at.connyduck.pixelcat.components.profile
import android.content.Context
import android.content.Intent
import android.os.Bundle
-import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/dagger/NetworkModule.kt b/app/src/main/kotlin/at/connyduck/pixelcat/dagger/NetworkModule.kt
index 3a12c3a..87300ad 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/dagger/NetworkModule.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/dagger/NetworkModule.kt
@@ -21,12 +21,14 @@ package at.connyduck.pixelcat.dagger
import at.connyduck.pixelcat.BuildConfig
import at.connyduck.pixelcat.db.AccountManager
+import at.connyduck.pixelcat.model.Notification
import at.connyduck.pixelcat.network.FediverseApi
import at.connyduck.pixelcat.network.InstanceSwitchAuthInterceptor
import at.connyduck.pixelcat.network.RefreshTokenAuthenticator
import at.connyduck.pixelcat.network.UserAgentInterceptor
import at.connyduck.pixelcat.network.calladapter.NetworkResponseAdapterFactory
import com.squareup.moshi.Moshi
+import com.squareup.moshi.adapters.EnumJsonAdapter
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
import dagger.Module
import dagger.Provides
@@ -69,6 +71,7 @@ class NetworkModule {
fun providesMoshi(): Moshi {
return Moshi.Builder()
.add(Date::class.java, Rfc3339DateJsonAdapter())
+ .add(Notification.Type::class.java, EnumJsonAdapter.create(Notification.Type::class.java).withUnknownFallback(Notification.Type.UNKNOWN))
.build()
}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/db/AppDatabase.kt b/app/src/main/kotlin/at/connyduck/pixelcat/db/AppDatabase.kt
index 5178b6e..933da09 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/db/AppDatabase.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/db/AppDatabase.kt
@@ -22,12 +22,15 @@ package at.connyduck.pixelcat.db
import androidx.room.Database
import androidx.room.RoomDatabase
import at.connyduck.pixelcat.db.entitity.AccountEntity
+import at.connyduck.pixelcat.db.entitity.NotificationEntity
import at.connyduck.pixelcat.db.entitity.StatusEntity
-@Database(entities = [AccountEntity::class, StatusEntity::class], version = 1)
+@Database(entities = [AccountEntity::class, StatusEntity::class, NotificationEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun accountDao(): AccountDao
abstract fun statusDao(): TimelineDao
+
+ abstract fun notificationsDao(): NotificationsDao
}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/db/Converters.kt b/app/src/main/kotlin/at/connyduck/pixelcat/db/Converters.kt
index 3b8da9c..3a50c74 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/db/Converters.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/db/Converters.kt
@@ -21,6 +21,7 @@ package at.connyduck.pixelcat.db
import androidx.room.TypeConverter
import at.connyduck.pixelcat.model.Attachment
+import at.connyduck.pixelcat.model.Notification
import at.connyduck.pixelcat.model.Status
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
@@ -31,14 +32,10 @@ class Converters {
private val moshi = Moshi.Builder().build()
@TypeConverter
- fun visibilityToInt(visibility: Status.Visibility): String {
- return visibility.name
- }
+ fun visibilityToString(visibility: Status.Visibility) = visibility.name
@TypeConverter
- fun stringToVisibility(visibility: String): Status.Visibility {
- return Status.Visibility.valueOf(visibility)
- }
+ fun stringToVisibility(visibility: String) = Status.Visibility.valueOf(visibility)
@TypeConverter
fun attachmentListToJson(attachmentList: List?): String {
@@ -62,12 +59,14 @@ class Converters {
}
@TypeConverter
- fun dateToLong(date: Date): Long {
- return date.time
- }
+ fun dateToLong(date: Date) = date.time
@TypeConverter
- fun longToDate(date: Long): Date {
- return Date(date)
- }
+ fun longToDate(date: Long) = Date(date)
+
+ @TypeConverter
+ fun notificationTypeToString(type: Notification.Type) = type.name
+
+ @TypeConverter
+ fun stringToNotificationType(type: String) = Notification.Type.valueOf(type)
}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/db/NotificationsDao.kt b/app/src/main/kotlin/at/connyduck/pixelcat/db/NotificationsDao.kt
new file mode 100644
index 0000000..af7abe1
--- /dev/null
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/db/NotificationsDao.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Conny Duck
+ *
+ * This file is part of Pixelcat.
+ *
+ * Pixelcat is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pixelcat is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.connyduck.pixelcat.db
+
+import androidx.paging.PagingSource
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import at.connyduck.pixelcat.db.entitity.NotificationEntity
+
+@Dao
+interface NotificationsDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertOrReplace(notification: NotificationEntity)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertOrReplace(statuses: List)
+
+ @Delete
+ suspend fun delete(status: NotificationEntity)
+
+ @Query("SELECT * FROM NotificationEntity WHERE accountId = :accountId ORDER BY LENGTH(id) DESC, id DESC")
+ fun notifications(accountId: Long): PagingSource
+
+ @Query("DELETE FROM NotificationEntity WHERE accountId = :accountId")
+ suspend fun clearAll(accountId: Long)
+}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/NotificationEntity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/NotificationEntity.kt
new file mode 100644
index 0000000..8bc91b6
--- /dev/null
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/NotificationEntity.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 Conny Duck
+ *
+ * This file is part of Pixelcat.
+ *
+ * Pixelcat is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pixelcat is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.connyduck.pixelcat.db.entitity
+
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.TypeConverters
+import at.connyduck.pixelcat.db.Converters
+import at.connyduck.pixelcat.model.Notification
+
+@Entity(primaryKeys = ["accountId", "id"])
+@TypeConverters(Converters::class)
+data class NotificationEntity(
+ val accountId: Long,
+ val type: Notification.Type,
+ val id: String,
+ @Embedded(prefix = "a_") val account: TimelineAccountEntity,
+ @Embedded(prefix = "s_") val status: StatusEntity?
+)
+
+fun Notification.toEntity(accountId: Long) = NotificationEntity(
+ accountId = accountId,
+ type = type,
+ id = id,
+ account = account.toEntity(accountId),
+ status = status?.toEntity(accountId, 0, false)
+)
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineStatusEntity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/StatusEntity.kt
similarity index 100%
rename from app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineStatusEntity.kt
rename to app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/StatusEntity.kt
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt
index 02973f5..a4b410b 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt
@@ -19,14 +19,10 @@
package at.connyduck.pixelcat.db.entitity
-import androidx.room.Entity
import at.connyduck.pixelcat.model.Account
-@Entity(
- primaryKeys = ["serverId", "timelineUserId"]
-)
data class TimelineAccountEntity(
- val serverId: Long,
+ val accountId: Long,
val id: String,
val localUsername: String,
val username: String,
@@ -35,8 +31,8 @@ data class TimelineAccountEntity(
val avatar: String
)
-fun Account.toEntity(serverId: Long) = TimelineAccountEntity(
- serverId = serverId,
+fun Account.toEntity(accountId: Long) = TimelineAccountEntity(
+ accountId = accountId,
id = id,
localUsername = localUsername,
username = username,
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/model/Notification.kt b/app/src/main/kotlin/at/connyduck/pixelcat/model/Notification.kt
new file mode 100644
index 0000000..c2112aa
--- /dev/null
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/model/Notification.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 Conny Duck
+ *
+ * This file is part of Pixelcat.
+ *
+ * Pixelcat is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pixelcat is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.connyduck.pixelcat.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class Notification(
+ val type: Type,
+ val id: String,
+ val account: Account,
+ val status: Status?
+) {
+
+ @JsonClass(generateAdapter = false)
+ enum class Type {
+ UNKNOWN,
+ @Json(name = "mention")
+ MENTION,
+ @Json(name = "reblog")
+ REBLOG,
+ @Json(name = "favourite")
+ FAVOURITE,
+ @Json(name = "follow")
+ FOLLOW,
+ @Json(name = "follow_request")
+ FOLLOW_REQUEST,
+ @Json(name = "poll")
+ POLL
+ }
+}
diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/network/FediverseApi.kt b/app/src/main/kotlin/at/connyduck/pixelcat/network/FediverseApi.kt
index a7c64ba..5f63721 100644
--- a/app/src/main/kotlin/at/connyduck/pixelcat/network/FediverseApi.kt
+++ b/app/src/main/kotlin/at/connyduck/pixelcat/network/FediverseApi.kt
@@ -24,6 +24,7 @@ import at.connyduck.pixelcat.model.Account
import at.connyduck.pixelcat.model.AppCredentials
import at.connyduck.pixelcat.model.Attachment
import at.connyduck.pixelcat.model.NewStatus
+import at.connyduck.pixelcat.model.Notification
import at.connyduck.pixelcat.model.Relationship
import at.connyduck.pixelcat.model.Status
import at.connyduck.pixelcat.model.StatusContext
@@ -40,6 +41,7 @@ import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
+@JvmSuppressWildcards
interface FediverseApi {
companion object {
@@ -199,4 +201,12 @@ interface FediverseApi {
suspend fun statusContext(
@Path("id") statusId: String
): NetworkResponse
+
+ @GET("api/v1/notifications")
+ suspend fun notifications(
+ @Query("max_id") maxId: String? = null,
+ @Query("since_id") sinceId: String? = null,
+ @Query("limit") limit: Int? = null,
+ @Query("exclude_types[]") excludes: Set? = null
+ ): NetworkResponse>
}
diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml
index 38d5870..14f7a0f 100644
--- a/app/src/main/res/layout/fragment_notifications.xml
+++ b/app/src/main/res/layout/fragment_notifications.xml
@@ -15,23 +15,24 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/ic_cat_small"
- app:title="@string/app_name"
+ app:title="@string/title_notifications"
app:titleTextAppearance="@style/TextAppearanceToolbar"
app:titleTextColor="?attr/colorPrimary" />
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_notification.xml b/app/src/main/res/layout/item_notification.xml
new file mode 100644
index 0000000..781ea4d
--- /dev/null
+++ b/app/src/main/res/layout/item_notification.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_notification_follow.xml b/app/src/main/res/layout/item_notification_follow.xml
new file mode 100644
index 0000000..b90cfba
--- /dev/null
+++ b/app/src/main/res/layout/item_notification_follow.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/navigation.xml b/app/src/main/res/menu/navigation.xml
index b3b09bf..c886cf8 100644
--- a/app/src/main/res/menu/navigation.xml
+++ b/app/src/main/res/menu/navigation.xml
@@ -9,12 +9,12 @@
+ android:title="@string/title_search"/>
+ android:title="@string/title_compose"/>
+ android:title="@string/title_profile"/>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0a63e06..b1c6d05 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,8 +1,10 @@
Pixelcat
Home
- Dashboard
+ Search
+ Compose new status
Notifications
+ Profile
Login
Which instance?
@@ -79,7 +81,10 @@
An unexpected error occurred
Failed to connect. Please check your internet connection-
- Replying to $1%s
+ Replying to %1$s
+ %1$s boosted your post
+ %1$s liked your post
+ %1$s followed you