Merge branch 'notifications' into 'master'
Notifications See merge request pixeldroid/PixelDroid!371
This commit is contained in:
commit
f539379741
|
@ -16,6 +16,7 @@ android {
|
|||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.3'
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
@ -100,6 +101,8 @@ android {
|
|||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
/**
|
||||
* AndroidX dependencies:
|
||||
*/
|
||||
|
@ -123,7 +126,8 @@ dependencies {
|
|||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation "androidx.activity:activity-ktx:1.3.1"
|
||||
implementation 'androidx.fragment:fragment-ktx:1.3.6'
|
||||
implementation "androidx.work:work-runtime-ktx:2.5.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.6.0"
|
||||
implementation 'androidx.work:work-testing:2.6.0'
|
||||
|
||||
// Use the most recent version of CameraX
|
||||
def cameraX_version = '1.0.1'
|
||||
|
@ -158,9 +162,9 @@ dependencies {
|
|||
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
|
||||
implementation 'io.reactivex.rxjava3:rxjava:3.1.1'
|
||||
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
|
||||
implementation 'com.github.connyduck:sparkbutton:4.1.0'
|
||||
|
||||
|
||||
|
@ -180,14 +184,14 @@ dependencies {
|
|||
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
|
||||
implementation 'com.mikepenz:materialdrawer:8.4.2'
|
||||
implementation 'com.mikepenz:materialdrawer:8.4.3'
|
||||
// Add for NavController support
|
||||
implementation 'com.mikepenz:materialdrawer-nav:8.4.2'
|
||||
|
||||
//iconics
|
||||
implementation "com.mikepenz:iconics-core:5.3.0"
|
||||
implementation 'com.mikepenz:iconics-core:5.3.1'
|
||||
implementation 'com.mikepenz:materialdrawer-iconics:8.4.2'
|
||||
implementation "com.mikepenz:iconics-views:5.3.0"
|
||||
implementation 'com.mikepenz:iconics-views:5.3.1'
|
||||
implementation 'com.mikepenz:google-material-typeface:4.0.0.1-kotlin@aar'
|
||||
|
||||
|
||||
|
|
|
@ -544,6 +544,12 @@
|
|||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/topic/libraries/architecture/index.html
|
||||
- artifact: androidx.work:work-testing:+
|
||||
name: work-testing
|
||||
copyrightHolder: Google Inc.
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/work#2.6.0
|
||||
- artifact: androidx.work:work-runtime-ktx:+
|
||||
name: work-runtime-ktx
|
||||
copyrightHolder: Google Inc.
|
||||
|
@ -628,8 +634,8 @@
|
|||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://github.com/square/retrofit
|
||||
- artifact: com.squareup.retrofit2:adapter-rxjava2:+
|
||||
name: adapter-rxjava2
|
||||
- artifact: com.squareup.retrofit2:adapter-rxjava3:+
|
||||
name: adapter-rxjava3
|
||||
copyrightHolder: Square, Inc.
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
|
@ -646,13 +652,13 @@
|
|||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://square.github.io/okhttp/
|
||||
- artifact: io.reactivex.rxjava2:rxandroid:+
|
||||
- artifact: io.reactivex.rxjava3:rxandroid:+
|
||||
name: rxandroid
|
||||
copyrightHolder: Netflix, Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://github.com/ReactiveX/RxAndroid
|
||||
- artifact: io.reactivex.rxjava2:rxjava:+
|
||||
- artifact: io.reactivex.rxjava3:rxjava:+
|
||||
name: rxjava
|
||||
copyrightHolder: Netflix, Inc.
|
||||
license: The Apache Software License, Version 2.0
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
@ -73,12 +74,12 @@ class IntentTest {
|
|||
"https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0",
|
||||
"https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0",
|
||||
"", "", false, emptyList(), null,
|
||||
"2021-02-11T23:44:03.000000Z", 0, 1, 2,
|
||||
Instant.parse("2021-02-11T23:44:03.000000Z"), 0, 1, 2,
|
||||
null, null, false, null)
|
||||
val expectedIntent: Matcher<Intent> = CoreMatchers.allOf(
|
||||
IntentMatchers.hasExtra(ACCOUNT_TAG, account)
|
||||
)
|
||||
|
||||
"2021-02-11T23:44:03.000000Z"
|
||||
waitForView(R.id.description)
|
||||
|
||||
//Click the mention
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
|
|||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import org.hamcrest.CoreMatchers.anyOf
|
||||
import org.pixeldroid.app.testUtility.*
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.junit.After
|
||||
|
@ -81,7 +82,11 @@ class MockedServerTest {
|
|||
|
||||
waitForView(R.id.username)
|
||||
|
||||
onView(withId(R.id.username)).check(matches(withSubstring("User ")))
|
||||
onView(withId(R.id.username)).check(matches(anyOf(
|
||||
withSubstring("User "),
|
||||
withSubstring("PixelDroid Developer"),
|
||||
withSubstring("Testi Testo")
|
||||
)))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
package org.pixeldroid.app
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.*
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.testing.TestListenableWorkerBuilder
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.MatcherAssert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.pixeldroid.app.settings.AboutActivity
|
||||
import org.pixeldroid.app.testUtility.*
|
||||
import org.pixeldroid.app.utils.api.objects.Account
|
||||
import org.pixeldroid.app.utils.api.objects.Notification
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
|
||||
import java.time.Instant
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
class NotificationWorkerTest {
|
||||
private lateinit var context: Context
|
||||
private lateinit var activityScenario: ActivityScenario<AboutActivity>
|
||||
private val uiDevice by lazy { UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) }
|
||||
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
private val secondToLatestNotification: Notification =
|
||||
Notification(
|
||||
id = "1",
|
||||
type = Notification.NotificationType.follow,
|
||||
created_at = Instant.parse("2021-09-19T19:23:30Z"),
|
||||
account = Account(
|
||||
id = "344399325768278017",
|
||||
username = "pixeldroid",
|
||||
acct = "pixeldroid",
|
||||
url = "https://testing.pixeldroid.org/pixeldroid",
|
||||
display_name = "PixelDroid",
|
||||
note = "",
|
||||
avatar = "https://testing.pixeldroid.org/storage/avatars/default.jpg?v=0",
|
||||
avatar_static = null,
|
||||
header = null,
|
||||
header_static = null,
|
||||
locked = false,
|
||||
emojis = null,
|
||||
discoverable = null,
|
||||
created_at = Instant.parse("1970-01-01T00:00:00Z"),
|
||||
statuses_count = 0,
|
||||
followers_count = 0,
|
||||
following_count = 1,
|
||||
moved = null,
|
||||
fields = null,
|
||||
bot = null,
|
||||
source = null
|
||||
),
|
||||
status = null,
|
||||
user_id = "344399082242686977",
|
||||
instance_uri = "https://testing.pixeldroid.org"
|
||||
)
|
||||
@Before
|
||||
fun setup() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
testiTestoInstance
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
testiTesto
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
db.notificationDao().insertAll(listOf(secondToLatestNotification))
|
||||
}
|
||||
|
||||
db.close()
|
||||
|
||||
activityScenario = ActivityScenario.launch(AboutActivity::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationWorker() {
|
||||
val expectedAppName = context.getString(R.string.app_name)
|
||||
val expectedText = "user1 followed you"
|
||||
|
||||
// Run the worker synchronously
|
||||
val worker = TestListenableWorkerBuilder<NotificationsWorker>(context).build()
|
||||
val result = worker.startWork().get()
|
||||
|
||||
// Check worker returns success (which doesn't mean much, but is a good start)
|
||||
MatcherAssert.assertThat(result, CoreMatchers.`is`(ListenableWorker.Result.success()))
|
||||
|
||||
//Open notification shade
|
||||
uiDevice.openNotification()
|
||||
uiDevice.wait(Until.hasObject(By.textStartsWith(expectedAppName)), 5000)
|
||||
|
||||
val text: UiObject2 = uiDevice.findObject(By.textStartsWith(expectedText))
|
||||
text.click()
|
||||
|
||||
uiDevice.wait(Until.hasObject(By.textStartsWith(expectedText)), 5000)
|
||||
waitForView(R.id.notification_type)
|
||||
onView(first(withId(R.id.notification_type)))
|
||||
.check(matches(withText(expectedText)))
|
||||
|
||||
}
|
||||
}
|
|
@ -27,6 +27,9 @@ import org.junit.*
|
|||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
@ -40,7 +43,7 @@ class PostTest {
|
|||
|
||||
@Before
|
||||
fun before(){
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
context = getInstrumentation().targetContext
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
|
@ -66,7 +69,8 @@ class PostTest {
|
|||
username = "SQDFSQDF",
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment)
|
||||
media_attachments = listOf(attachment),
|
||||
created_at = Instant.now().minusSeconds(3600)
|
||||
)
|
||||
val intent = Intent(context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
|
@ -102,7 +106,8 @@ class PostTest {
|
|||
username = "douze",
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment1, attachment2)
|
||||
media_attachments = listOf(attachment1, attachment2),
|
||||
created_at = Instant.now().minusSeconds(3600)
|
||||
)
|
||||
val intent = Intent(context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
|
@ -134,7 +139,8 @@ class PostTest {
|
|||
username = "douze",
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment)
|
||||
media_attachments = listOf(attachment),
|
||||
created_at = Instant.now().minusSeconds(3600)
|
||||
)
|
||||
val intent = Intent(context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
|
@ -165,7 +171,8 @@ class PostTest {
|
|||
username = "douze",
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment1, attachment2)
|
||||
media_attachments = listOf(attachment1, attachment2),
|
||||
created_at = Instant.now().minusSeconds(3600)
|
||||
)
|
||||
val intent = Intent(context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
|
@ -181,13 +188,13 @@ class PostTest {
|
|||
@Test
|
||||
fun getNLikesReturnsCorrectFormat() {
|
||||
val status = Status(id="140364967936397312", uri="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
created_at= SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'").parse("2020-03-03T08:00:16.000000Z"),
|
||||
created_at= OffsetDateTime.parse("2020-03-03T08:00:16+00:00").toInstant(),
|
||||
account= Account(id="115114166443970560", username="Miike", acct="Miike",
|
||||
url="https://pixelfed.de/Miike", display_name="Miike Duart", note="",
|
||||
avatar="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
avatar_static="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
header="", header_static="", locked=false, emojis= emptyList(), discoverable=false,
|
||||
created_at="2019-12-24T15:42:35.000000Z", statuses_count=71, followers_count=14,
|
||||
created_at=Instant.parse("2019-12-24T15:42:35.000000Z"), statuses_count=71, followers_count=14,
|
||||
following_count=0, moved=null, fields=null, bot=false, source=null),
|
||||
content="""Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""",
|
||||
visibility=Status.Visibility.public, sensitive=false, spoiler_text="",
|
||||
|
@ -208,13 +215,13 @@ class PostTest {
|
|||
@Test
|
||||
fun getNSharesReturnsCorrectFormat() {
|
||||
val status = Status(id="140364967936397312", uri="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
created_at= SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'").parse("2020-03-03T08:00:16.000000Z"),
|
||||
created_at= Instant.parse("2020-03-03T08:00:16.00Z"),
|
||||
account= Account(id="115114166443970560", username="Miike", acct="Miike",
|
||||
url="https://pixelfed.de/Miike", display_name="Miike Duart", note="",
|
||||
avatar="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
avatar_static="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
header="", header_static="", locked=false, emojis= emptyList(), discoverable=false,
|
||||
created_at="2019-12-24T15:42:35.000000Z", statuses_count=71, followers_count=14,
|
||||
created_at=Instant.parse("2019-12-24T15:42:35.000000Z"), statuses_count=71, followers_count=14,
|
||||
following_count=0, moved=null, fields=null, bot=false, source=null),
|
||||
content="""Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""",
|
||||
visibility=Status.Visibility.public, sensitive=false, spoiler_text="",
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.junit.After
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.time.Instant
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ProfileTest {
|
||||
|
@ -37,7 +38,7 @@ class ProfileTest {
|
|||
db.close()
|
||||
|
||||
val intent = Intent(context, ProfileActivity::class.java)
|
||||
val account = Account(id = "265472486651596800", username = "pixeldroid", acct = "pixeldroid", url = "https://testing2.pixeldroid.org/pixeldroid", display_name = "PixelDroid Developer", avatar = "https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0", avatar_static = "https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0", locked = false, emojis = arrayListOf(), discoverable = null, created_at = "2021-02-11T13:32:53.000000Z", statuses_count = 1, followers_count = 1, following_count = 1, moved = null, fields = null, bot = false, source = null)
|
||||
val account = Account(id="344399325768278017", username="pixeldroid", acct="pixeldroid", url="https://testing.pixeldroid.org/pixeldroid", display_name="PixelDroid Developer", note="", avatar="https://testing.pixeldroid.org/storage/avatars/default.jpg?v=0", avatar_static="https://testing.pixeldroid.org/storage/avatars/default.jpg?v=0", header="", header_static="", locked=false, emojis= emptyList(), discoverable=null, created_at=Instant.parse("2021-09-17T08:39:57Z"), statuses_count=0, followers_count=1, following_count=1, moved=null, fields=null, bot=false, source=null)
|
||||
intent.putExtra(Account.ACCOUNT_TAG, account)
|
||||
activityScenario = ActivityScenario.launch(intent)
|
||||
onView(withId(R.id.profileRefreshLayout)).perform(swipeDown())
|
||||
|
@ -86,7 +87,7 @@ class ProfileTest {
|
|||
waitForView(R.id.account_entry_username)
|
||||
|
||||
// Open follower's profile
|
||||
onView(ViewMatchers.withText("testi testo")).perform((ViewActions.click()))
|
||||
onView(ViewMatchers.withText("Testi Testo")).perform((ViewActions.click()))
|
||||
|
||||
waitForView(R.id.editButton)
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import org.pixeldroid.app.databinding.ActivityLoginBinding
|
||||
import org.pixeldroid.app.utils.*
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
|
@ -16,8 +18,13 @@ import org.pixeldroid.app.utils.api.objects.*
|
|||
import org.pixeldroid.app.utils.db.addUser
|
||||
import org.pixeldroid.app.utils.db.storeInstance
|
||||
import kotlinx.coroutines.*
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
|
||||
import org.pixeldroid.app.utils.notificationsWorker.makeChannelGroupId
|
||||
import org.pixeldroid.app.utils.notificationsWorker.makeNotificationChannels
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.lang.NullPointerException
|
||||
|
||||
/**
|
||||
Overview of the flow of the login process: (boxes are requests done in parallel,
|
||||
|
@ -310,14 +317,40 @@ class LoginActivity : BaseActivity() {
|
|||
clientSecret = clientSecret
|
||||
)
|
||||
apiHolder.setToCurrentUser()
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
} catch (exception: IOException) {
|
||||
return failedRegistration(getString(R.string.verify_credentials))
|
||||
} catch (exception: HttpException) {
|
||||
return failedRegistration(getString(R.string.verify_credentials))
|
||||
}
|
||||
|
||||
fetchNotifications()
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
// Fetch the latest notifications of this account, to avoid launching old notifications
|
||||
private suspend fun fetchNotifications() {
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
try {
|
||||
val notifications = apiHolder.api!!.notifications()
|
||||
|
||||
notifications.forEach{it.user_id = user.user_id; it.instance_uri = user.instance_uri}
|
||||
|
||||
db.notificationDao().insertAll(notifications)
|
||||
} catch (exception: IOException) {
|
||||
return failedRegistration(getString(R.string.login_notifications))
|
||||
} catch (exception: HttpException) {
|
||||
return failedRegistration(getString(R.string.login_notifications))
|
||||
} catch (exception: NullPointerException) {
|
||||
return failedRegistration(getString(R.string.login_notifications))
|
||||
}
|
||||
|
||||
makeNotificationChannels(
|
||||
applicationContext,
|
||||
user.fullHandle,
|
||||
makeChannelGroupId(user)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,10 +41,15 @@ import org.pixeldroid.app.searchDiscover.SearchDiscoverFragment
|
|||
import org.pixeldroid.app.settings.SettingsActivity
|
||||
import org.pixeldroid.app.utils.BaseActivity
|
||||
import org.pixeldroid.app.utils.db.addUser
|
||||
import org.pixeldroid.app.utils.notificationsWorker.enablePullNotifications
|
||||
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
|
||||
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import org.pixeldroid.app.utils.hasInternet
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.INSTANCE_NOTIFICATION_TAG
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_NOTIFICATION_TAG
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.USER_NOTIFICATION_TAG
|
||||
import org.pixeldroid.app.utils.notificationsWorker.removeNotificationChannelsFromAccount
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
|
@ -56,6 +61,7 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
companion object {
|
||||
const val ADD_ACCOUNT_IDENTIFIER: Long = -13
|
||||
const val LOG_OUT_REQUESTED = "LOG_OUT_REQUESTED"
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
@ -70,6 +76,8 @@ class MainActivity : BaseActivity() {
|
|||
//get the currently active user
|
||||
user = db.userDao().getActiveUser()
|
||||
|
||||
if (notificationFromOtherUser()) return
|
||||
|
||||
//Check if we have logged in and gotten an access token
|
||||
if (user == null) {
|
||||
finish()
|
||||
|
@ -96,9 +104,43 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
)
|
||||
setupTabs(tabs)
|
||||
|
||||
val showNotification: Boolean = intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false)
|
||||
|
||||
if(showNotification){
|
||||
binding.viewPager.currentItem = 3
|
||||
}
|
||||
|
||||
enablePullNotifications(this)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the activity was launched from a notification from another account than the
|
||||
// current active one, and if so switches to that account
|
||||
private fun notificationFromOtherUser(): Boolean {
|
||||
val userOfNotification: String? = intent.extras?.getString(USER_NOTIFICATION_TAG)
|
||||
val instanceOfNotification: String? = intent.extras?.getString(INSTANCE_NOTIFICATION_TAG)
|
||||
if (userOfNotification != null && instanceOfNotification != null
|
||||
&& (userOfNotification != user?.user_id
|
||||
|| instanceOfNotification != user?.instance_uri)
|
||||
) {
|
||||
|
||||
switchUser(userOfNotification)
|
||||
|
||||
val newIntent = Intent(this, MainActivity::class.java)
|
||||
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
|
||||
if (intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false)) {
|
||||
newIntent.putExtra(SHOW_NOTIFICATION_TAG, true)
|
||||
}
|
||||
|
||||
finish()
|
||||
startActivity(newIntent)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setupDrawer() {
|
||||
binding.mainDrawerButton.setOnClickListener{
|
||||
binding.drawerLayout.open()
|
||||
|
@ -173,6 +215,9 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
private fun logOut(){
|
||||
finish()
|
||||
|
||||
removeNotificationChannelsFromAccount(applicationContext, user)
|
||||
|
||||
db.runInTransaction {
|
||||
db.userDao().deleteActiveUsers()
|
||||
|
||||
|
@ -225,9 +270,8 @@ class MainActivity : BaseActivity() {
|
|||
return false
|
||||
}
|
||||
|
||||
db.userDao().deActivateActiveUsers()
|
||||
db.userDao().activateUser(profile.identifier.toString())
|
||||
apiHolder.setToCurrentUser()
|
||||
switchUser(profile.identifier.toString())
|
||||
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
|
||||
|
@ -237,6 +281,12 @@ class MainActivity : BaseActivity() {
|
|||
return false
|
||||
}
|
||||
|
||||
private fun switchUser(userId: String) {
|
||||
db.userDao().deActivateActiveUsers()
|
||||
db.userDao().activateUser(userId)
|
||||
apiHolder.setToCurrentUser()
|
||||
}
|
||||
|
||||
private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem {
|
||||
return PrimaryDrawerItem()
|
||||
.apply {
|
||||
|
@ -262,7 +312,7 @@ class MainActivity : BaseActivity() {
|
|||
iconUrl = user.avatar_static
|
||||
isNameShown = true
|
||||
identifier = user.user_id.toLong()
|
||||
descriptionText = "@${user.username}@${user.instance_uri.removePrefix("https://")}"
|
||||
descriptionText = user.fullHandle
|
||||
}
|
||||
}.toMutableList()
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@ import androidx.core.net.toFile
|
|||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.pixeldroid.app.MainActivity
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.databinding.ActivityPostCreationBinding
|
||||
|
@ -33,9 +36,6 @@ import org.pixeldroid.app.utils.BaseActivity
|
|||
import org.pixeldroid.app.utils.api.objects.Attachment
|
||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.HttpException
|
||||
import java.io.File
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.pixeldroid.app.postCreation
|
||||
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.RequestBody
|
||||
|
|
|
@ -10,7 +10,6 @@ import android.text.style.URLSpan
|
|||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.text.toSpanned
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import org.pixeldroid.app.R
|
||||
|
@ -22,6 +21,8 @@ import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
|||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.text.ParseException
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.util.*
|
||||
|
||||
fun fromHtml(html: String): Spanned {
|
||||
|
@ -128,18 +129,18 @@ fun parseHTMLText(
|
|||
}
|
||||
|
||||
|
||||
fun setTextViewFromISO8601(date: Date, textView: TextView, absoluteTime: Boolean, context: Context) {
|
||||
val now = Date().time
|
||||
fun setTextViewFromISO8601(date: Instant, textView: TextView, absoluteTime: Boolean, context: Context) {
|
||||
val now = Date.from(Instant.now()).time
|
||||
|
||||
try {
|
||||
val then = date.time
|
||||
val formattedDate = android.text.format.DateUtils
|
||||
.getRelativeTimeSpanString(then, now,
|
||||
android.text.format.DateUtils.SECOND_IN_MILLIS,
|
||||
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE)
|
||||
val then = Date.from(date).time
|
||||
val formattedDate: String = android.text.format.DateUtils
|
||||
.getRelativeTimeSpanString(then, now,
|
||||
android.text.format.DateUtils.SECOND_IN_MILLIS,
|
||||
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE).toString()
|
||||
|
||||
textView.text = if(absoluteTime) context.getString(R.string.posted_on).format(date)
|
||||
else "$formattedDate"
|
||||
else formattedDate
|
||||
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.pixeldroid.app.settings
|
|||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
|
@ -72,13 +73,18 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
|
|||
putBoolean("restartMain", true)
|
||||
}
|
||||
intent.putExtras(savedInstanceState)
|
||||
super.startActivity(intent)
|
||||
finish()
|
||||
super.startActivity(intent)
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
|
||||
//Hide Notification setting for Android versions where it doesn't work
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
preferenceScreen.removePreference(preferenceManager.findPreference("notification"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package org.pixeldroid.app.utils.api
|
||||
|
||||
import com.google.gson.*
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import org.pixeldroid.app.utils.api.objects.*
|
||||
import io.reactivex.Observable
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.OkHttpClient
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
|
@ -10,10 +11,13 @@ import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
|||
import org.pixeldroid.app.utils.di.TokenAuthenticator
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.*
|
||||
import retrofit2.http.Field
|
||||
import java.time.Instant
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
||||
/*
|
||||
Implements the Pixelfed API
|
||||
|
@ -29,14 +33,28 @@ interface PixelfedAPI {
|
|||
fun createFromUrl(baseUrl: String): PixelfedAPI {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create(gSonInstance))
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
.build().create(PixelfedAPI::class.java)
|
||||
}
|
||||
|
||||
private var gSonInstance: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(
|
||||
Instant::class.java,
|
||||
JsonDeserializer { json: JsonElement, _, _ ->
|
||||
DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(
|
||||
json.asString, Instant::from
|
||||
)
|
||||
} as JsonDeserializer<Instant>).registerTypeAdapter(
|
||||
Instant::class.java,
|
||||
JsonSerializer { src: Instant, _, _ ->
|
||||
JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(src))
|
||||
})
|
||||
.create()
|
||||
|
||||
private val intermediate: Retrofit.Builder = Retrofit.Builder()
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create(gSonInstance))
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
|
||||
|
||||
fun apiForUser(
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.pixeldroid.app.utils.api.PixelfedAPI
|
|||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
/*
|
||||
Represents a user and their associated profile.
|
||||
|
@ -32,7 +33,7 @@ data class Account(
|
|||
val emojis: List<Emoji>? = null,
|
||||
val discoverable: Boolean? = true,
|
||||
//Statistical attributes
|
||||
val created_at: String? = "", //ISO 8601 Datetime (maybe can use a date type)
|
||||
val created_at: Instant? = null, //ISO 8601 Datetime
|
||||
val statuses_count: Int? = 0,
|
||||
val followers_count: Int? = 0,
|
||||
val following_count: Int? = 0,
|
||||
|
|
|
@ -5,7 +5,7 @@ import androidx.room.ForeignKey
|
|||
import androidx.room.Index
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import java.io.Serializable
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
|
||||
/*
|
||||
Represents a notification of an event relevant to the user.
|
||||
|
@ -27,7 +27,7 @@ data class Notification(
|
|||
//Required attributes
|
||||
override val id: String,
|
||||
val type: NotificationType?,
|
||||
val created_at: Date?, //ISO 8601 Datetime
|
||||
val created_at: Instant? = null, //ISO 8601 Datetime
|
||||
val account: Account?,
|
||||
//Optional attributes
|
||||
val status: Status? = null,
|
||||
|
|
|
@ -1,5 +1,22 @@
|
|||
package org.pixeldroid.app.utils.api.objects
|
||||
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
class Poll : Serializable
|
||||
data class Poll (
|
||||
val id: String?,
|
||||
val expires_at: Instant? = null, //ISO 8601 Datetime, or null if poll does not end
|
||||
val expired: Boolean?,
|
||||
val multiple: Boolean, //Does the poll allow multiple-choice answers?
|
||||
val votes_count: Int?,
|
||||
val voters_count: Int?,
|
||||
val voted: Boolean?, //null if gotten without user token
|
||||
val own_votes: List<Int?>?,
|
||||
val options: List<Option?>?,
|
||||
val emojis: List<Emoji?>?
|
||||
): Serializable {
|
||||
data class Option(
|
||||
val title: String?,
|
||||
val votes_count: Int? //null if result not published yet
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.pixeldroid.app.R
|
|||
import org.pixeldroid.app.posts.getDomain
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
Represents a status posted by an account.
|
||||
|
@ -24,7 +24,7 @@ open class Status(
|
|||
//Base attributes
|
||||
override val id: String,
|
||||
val uri: String? = "",
|
||||
val created_at: Date? = Date(0), //ISO 8601 Datetime
|
||||
val created_at: Instant? = null, //ISO 8601 Datetime
|
||||
val account: Account?,
|
||||
val content: String? = "", //HTML
|
||||
val visibility: Visibility? = Visibility.public,
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.pixeldroid.app.utils.api.objects.Notification
|
|||
PublicFeedStatusDatabaseEntity::class,
|
||||
Notification::class
|
||||
],
|
||||
version = 3
|
||||
version = 4
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
|
|
@ -4,11 +4,27 @@ import androidx.room.TypeConverter
|
|||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.pixeldroid.app.utils.api.objects.*
|
||||
import java.time.Instant
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
class Converters {
|
||||
private val gson = Gson()
|
||||
|
||||
private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
|
||||
|
||||
private val instantFormatter = DateTimeFormatter.ISO_INSTANT
|
||||
|
||||
@TypeConverter
|
||||
fun toInstant(timestamp: String?): Instant? =
|
||||
timestamp?.let {
|
||||
instantFormatter.parse(it, Instant::from)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromInstant(time: Instant?): String? =
|
||||
time?.let { instantFormatter.format(it) }
|
||||
|
||||
|
||||
@TypeConverter
|
||||
fun listToJson(list: List<String>): String = gson.toJson(list)
|
||||
|
||||
|
@ -16,12 +32,6 @@ class Converters {
|
|||
fun jsonToList(json: String): List<String> =
|
||||
gson.fromJson(json, Array<String>::class.java).toList()
|
||||
|
||||
@TypeConverter
|
||||
fun dateToJson(date: Date): String = gson.toJson(date)
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToDate(json: String): Date = gson.fromJson(json, Date::class.java)
|
||||
|
||||
@TypeConverter
|
||||
fun accountToJson(account: Account): String = gson.toJson(account)
|
||||
|
||||
|
|
|
@ -33,12 +33,14 @@ interface UserDao {
|
|||
@Query("UPDATE users SET isActive=0")
|
||||
fun deActivateActiveUsers()
|
||||
|
||||
//TODO also check instance_uri
|
||||
@Query("UPDATE users SET isActive=1 WHERE user_id=:id")
|
||||
fun activateUser(id: String)
|
||||
|
||||
@Query("DELETE FROM users WHERE isActive=1")
|
||||
fun deleteActiveUsers()
|
||||
|
||||
//TODO also check instance_uri
|
||||
@Query("SELECT * FROM users WHERE user_id=:id LIMIT 1")
|
||||
fun getUserWithId(id: String): UserDatabaseEntity
|
||||
}
|
|
@ -12,9 +12,13 @@ interface NotificationDao: FeedContentDao<Notification> {
|
|||
override suspend fun clearFeedContent(userId: String, instanceUri: String)
|
||||
|
||||
@Query("""SELECT * FROM notifications WHERE user_id=:userId AND instance_uri=:instanceUri
|
||||
ORDER BY CAST(created_at AS FLOAT) DESC""")
|
||||
ORDER BY datetime(created_at) DESC""")
|
||||
override fun feedContent(userId: String, instanceUri: String): PagingSource<Int, Notification>
|
||||
|
||||
@Query("""SELECT * FROM notifications WHERE user_id=:userId AND instance_uri=:instanceUri
|
||||
ORDER BY datetime(created_at) DESC LIMIT 1""")
|
||||
fun latestNotification(userId: String, instanceUri: String): Notification?
|
||||
|
||||
@Query("DELETE FROM notifications WHERE user_id=:userId AND instance_uri=:instanceUri AND id=:id")
|
||||
override suspend fun delete(id: String, userId: String, instanceUri: String)
|
||||
}
|
|
@ -9,7 +9,7 @@ import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
|
|||
@Dao
|
||||
interface HomePostDao: FeedContentDao<HomeStatusDatabaseEntity> {
|
||||
@Query("""SELECT * FROM homePosts WHERE user_id=:userId AND instance_uri=:instanceUri
|
||||
ORDER BY CAST(created_at AS FLOAT)""")
|
||||
ORDER BY datetime(created_at) DESC""")
|
||||
override fun feedContent(userId: String, instanceUri: String): PagingSource<Int, HomeStatusDatabaseEntity>
|
||||
|
||||
@Query("DELETE FROM homePosts WHERE user_id=:userId AND instance_uri=:instanceUri")
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
|
|||
@Dao
|
||||
interface PublicPostDao: FeedContentDao<PublicFeedStatusDatabaseEntity> {
|
||||
@Query("""SELECT * FROM publicPosts WHERE user_id=:userId AND instance_uri=:instanceUri
|
||||
ORDER BY CAST(created_at AS FLOAT)""")
|
||||
ORDER BY datetime(created_at) DESC""")
|
||||
override fun feedContent(userId: String, instanceUri: String): PagingSource<Int, PublicFeedStatusDatabaseEntity>
|
||||
|
||||
@Query("DELETE FROM publicPosts WHERE user_id=:userId AND instance_uri=:instanceUri")
|
||||
|
|
|
@ -4,7 +4,7 @@ import androidx.room.Entity
|
|||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import org.pixeldroid.app.utils.api.objects.*
|
||||
import java.util.*
|
||||
import java.time.Instant
|
||||
|
||||
@Entity(
|
||||
tableName = "homePosts",
|
||||
|
@ -56,7 +56,7 @@ class HomeStatusDatabaseEntity(
|
|||
//Constructor to make Room happy. This sucks, and I know it.
|
||||
constructor(id: String,
|
||||
uri: String? = "",
|
||||
created_at: Date? = Date(0),
|
||||
created_at: Instant?,
|
||||
account: Account?,
|
||||
content: String? = "",
|
||||
visibility: Visibility? = Visibility.public,
|
||||
|
|
|
@ -4,7 +4,7 @@ import androidx.room.Entity
|
|||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import org.pixeldroid.app.utils.api.objects.*
|
||||
import java.util.*
|
||||
import java.time.Instant
|
||||
|
||||
@Entity(
|
||||
tableName = "publicPosts",
|
||||
|
@ -56,7 +56,7 @@ class PublicFeedStatusDatabaseEntity(
|
|||
//Constructor to make Room happy. This sucks, and I know it.
|
||||
constructor(id: String,
|
||||
uri: String? = "",
|
||||
created_at: Date? = Date(0),
|
||||
created_at: Instant?,
|
||||
account: Account?,
|
||||
content: String? = "",
|
||||
visibility: Visibility? = Visibility.public,
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.pixeldroid.app.utils.db.entities
|
|||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity(
|
||||
tableName = "users",
|
||||
|
@ -27,4 +28,7 @@ data class UserDatabaseEntity(
|
|||
val refreshToken: String?,
|
||||
val clientId: String,
|
||||
val clientSecret: String
|
||||
)
|
||||
): Serializable {
|
||||
val fullHandle: String
|
||||
get() = "@${username}@${instance_uri.removePrefix("https://")}"
|
||||
}
|
|
@ -7,6 +7,7 @@ import org.pixeldroid.app.utils.PixelDroidApplication
|
|||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.pixeldroid.app.utils.BaseFragment
|
||||
import dagger.Component
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
|
@ -16,6 +17,7 @@ interface ApplicationComponent {
|
|||
fun inject(application: PixelDroidApplication?)
|
||||
fun inject(activity: BaseActivity?)
|
||||
fun inject(feedFragment: BaseFragment)
|
||||
fun inject(notificationsWorker: NotificationsWorker)
|
||||
|
||||
val context: Context?
|
||||
val application: Application?
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package org.pixeldroid.app.utils.notificationsWorker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
fun enablePullNotifications(context: Context) {
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
val tag = "NOTIFICATION_PULL_TAG"
|
||||
workManager.cancelAllWorkByTag(tag)
|
||||
val workRequest: WorkRequest = PeriodicWorkRequestBuilder<NotificationsWorker>(
|
||||
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS,
|
||||
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, TimeUnit.MILLISECONDS
|
||||
)
|
||||
.addTag(tag)
|
||||
.setConstraints(
|
||||
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
|
||||
)
|
||||
.build()
|
||||
workManager.enqueue(workRequest)
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
package org.pixeldroid.app.utils.notificationsWorker
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationChannelGroup
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import org.pixeldroid.app.MainActivity
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.posts.PostActivity
|
||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser
|
||||
import org.pixeldroid.app.utils.api.objects.Notification
|
||||
import org.pixeldroid.app.utils.api.objects.Notification.NotificationType.*
|
||||
import org.pixeldroid.app.utils.api.objects.Status
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
||||
|
||||
class NotificationsWorker(
|
||||
context: Context,
|
||||
params: WorkerParameters
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
@Inject
|
||||
lateinit var db: AppDatabase
|
||||
@Inject
|
||||
lateinit var apiHolder: PixelfedAPIHolder
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
|
||||
(applicationContext as PixelDroidApplication).getAppComponent().inject(this)
|
||||
|
||||
val users: List<UserDatabaseEntity> = db.userDao().getAll()
|
||||
|
||||
for (user in users){
|
||||
val uniqueUserId = makeChannelGroupId(user)
|
||||
|
||||
val notificationsEnabledForUser = makeNotificationChannels(
|
||||
applicationContext,
|
||||
user.fullHandle,
|
||||
uniqueUserId
|
||||
)
|
||||
|
||||
//if notifications are disabled for this user, move on to next user
|
||||
if(!notificationsEnabledForUser) continue
|
||||
|
||||
// Get newest notification from database
|
||||
var previouslyLatestNotification: Notification? = db.notificationDao().latestNotification(user.user_id, user.instance_uri)
|
||||
|
||||
val api = apiForUser(user, db, apiHolder)
|
||||
|
||||
try {
|
||||
// Request notifications from server
|
||||
var newNotifications: List<Notification>? = api.notifications(
|
||||
since_id = previouslyLatestNotification?.id
|
||||
)
|
||||
|
||||
while (!newNotifications.isNullOrEmpty()
|
||||
&& newNotifications.map { it.created_at ?: Instant.MIN }
|
||||
.maxOrNull()!! > previouslyLatestNotification?.created_at ?: Instant.MIN
|
||||
) {
|
||||
// Add to db
|
||||
val filteredNewNotifications: List<Notification> = newNotifications.filter {
|
||||
it.created_at ?: Instant.MIN > previouslyLatestNotification?.created_at ?: Instant.MIN
|
||||
}.map {
|
||||
it.copy(user_id = user.user_id, instance_uri = user.instance_uri)
|
||||
}.sortedBy { it.created_at }
|
||||
|
||||
db.notificationDao().insertAll(filteredNewNotifications)
|
||||
|
||||
// Launch new notifications
|
||||
filteredNewNotifications.forEach {
|
||||
showNotification(it, user, uniqueUserId)
|
||||
}
|
||||
|
||||
previouslyLatestNotification =
|
||||
filteredNewNotifications.maxByOrNull { it.created_at ?: Instant.MIN }
|
||||
|
||||
// Request again
|
||||
newNotifications = api.notifications(
|
||||
since_id = previouslyLatestNotification?.id
|
||||
)
|
||||
}
|
||||
} catch (exception: IOException) {
|
||||
return Result.failure()
|
||||
} catch (exception: HttpException) {
|
||||
return Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun showNotification(
|
||||
notification: Notification,
|
||||
user: UserDatabaseEntity,
|
||||
uniqueUserId: String
|
||||
) {
|
||||
val intent: Intent = when (notification.type) {
|
||||
mention -> notification.status?.let {
|
||||
Intent(applicationContext, PostActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(Status.POST_TAG, notification.status)
|
||||
putExtra(Status.VIEW_COMMENTS_TAG, true)
|
||||
}
|
||||
} ?: Intent(applicationContext, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(SHOW_NOTIFICATION_TAG, true)
|
||||
}
|
||||
else -> Intent(applicationContext, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(SHOW_NOTIFICATION_TAG, true)
|
||||
}
|
||||
}.putExtra(USER_NOTIFICATION_TAG, user.user_id)
|
||||
.putExtra(INSTANCE_NOTIFICATION_TAG, user.instance_uri)
|
||||
|
||||
|
||||
val builder = NotificationCompat.Builder(applicationContext, makeChannelId(uniqueUserId, notification.type))
|
||||
.setSmallIcon(
|
||||
when (notification.type) {
|
||||
follow -> R.drawable.ic_follow
|
||||
mention -> R.drawable.mention_at_24dp
|
||||
reblog -> R.drawable.ic_reblog
|
||||
favourite -> R.drawable.ic_like_full
|
||||
comment -> R.drawable.ic_comment_empty
|
||||
poll -> R.drawable.poll
|
||||
null -> R.drawable.ic_comment_empty
|
||||
}
|
||||
)
|
||||
.setColor(Color.parseColor("#6200EE"))
|
||||
.setContentTitle(
|
||||
notification.account?.username?.let { username ->
|
||||
applicationContext.getString(
|
||||
when (notification.type) {
|
||||
follow -> R.string.followed_notification
|
||||
comment -> R.string.comment_notification
|
||||
mention -> R.string.mention_notification
|
||||
reblog -> R.string.shared_notification
|
||||
favourite -> R.string.liked_notification
|
||||
poll -> R.string.poll_notification
|
||||
null -> R.string.other_notification
|
||||
}
|
||||
).format(username)
|
||||
}
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
// Set the intent that will fire when the user taps the notification
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
.setAutoCancel(true)
|
||||
|
||||
if (notification.type == mention || notification.type == comment || notification.type == poll){
|
||||
builder.setContentText(notification.status?.content)
|
||||
}
|
||||
|
||||
builder.setGroup(uniqueUserId)
|
||||
|
||||
with(NotificationManagerCompat.from(applicationContext)) {
|
||||
// notificationId is a unique int for each notification
|
||||
notify((uniqueUserId + notification.id).hashCode(), builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SHOW_NOTIFICATION_TAG = "org.pixeldroid.app.SHOW_NOTIFICATION"
|
||||
const val INSTANCE_NOTIFICATION_TAG = "org.pixeldroid.app.USER_NOTIFICATION"
|
||||
const val USER_NOTIFICATION_TAG = "org.pixeldroid.app.INSTANCE_NOTIFICATION"
|
||||
|
||||
const val otherNotificationType = "other"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun makeNotificationChannels(context: Context, handle: String, channelGroupId: String): Boolean {
|
||||
val notificationManager: NotificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
// Create the NotificationChannel, but only on API 26+ because
|
||||
// the NotificationChannel class is new and not in the support library
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// The id of the group, hashed (since when creating the group, it may be truncated if too long)
|
||||
val hashedGroupId = channelGroupId.hashCode().toString()
|
||||
notificationManager.createNotificationChannelGroup(NotificationChannelGroup(hashedGroupId, handle))
|
||||
|
||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
|
||||
val channels: List<NotificationChannel> = listOf(
|
||||
NotificationChannel(makeChannelId(channelGroupId, follow), context.getString(R.string.followed_notification_channel), importance),
|
||||
NotificationChannel(makeChannelId(channelGroupId, mention), context.getString(R.string.mention_notification_channel), importance),
|
||||
NotificationChannel(makeChannelId(channelGroupId, reblog), context.getString(R.string.shared_notification_channel), importance),
|
||||
NotificationChannel(makeChannelId(channelGroupId, favourite), context.getString(R.string.liked_notification_channel), importance),
|
||||
NotificationChannel(makeChannelId(channelGroupId, comment), context.getString(R.string.comment_notification_channel), importance),
|
||||
NotificationChannel(makeChannelId(channelGroupId, poll), context.getString(R.string.poll_notification_channel), importance),
|
||||
NotificationChannel(makeChannelId(channelGroupId, null), context.getString(R.string.other_notification_channel), importance),
|
||||
).map {
|
||||
it.apply { group = hashedGroupId }
|
||||
}
|
||||
|
||||
// Register the channels with the system
|
||||
notificationManager.createNotificationChannels(channels)
|
||||
|
||||
//Return true if notifications are enabled, false if disabled
|
||||
return notificationManager.areNotificationsEnabled() and
|
||||
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val channelGroup =
|
||||
notificationManager.getNotificationChannelGroup(hashedGroupId)
|
||||
!channelGroup.isBlocked
|
||||
} else true) and
|
||||
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
!notificationManager.areNotificationsPaused()
|
||||
} else true) and
|
||||
!channels.all {
|
||||
notificationManager.getNotificationChannel(it.id).importance <= NotificationManager.IMPORTANCE_NONE
|
||||
}
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
notificationManager.areNotificationsEnabled()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [channelGroupId] is the id used to uniquely identify the group: for us it is a unique id
|
||||
* identifying a user consisting of the concatenation of the instance uri and user id.
|
||||
*/
|
||||
private fun makeChannelId(channelGroupId: String, type: Notification.NotificationType?): String =
|
||||
(channelGroupId + (type ?: NotificationsWorker.otherNotificationType)).hashCode().toString()
|
||||
|
||||
fun makeChannelGroupId(user: UserDatabaseEntity) = user.instance_uri + user.user_id
|
||||
|
||||
|
||||
fun removeNotificationChannelsFromAccount(context: Context, user: UserDatabaseEntity?) = user?.let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager: NotificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val channelGroupId = makeChannelGroupId(user)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
notificationManager.deleteNotificationChannelGroup(channelGroupId.hashCode().toString())
|
||||
} else {
|
||||
val types: MutableList<Notification.NotificationType?> =
|
||||
Notification.NotificationType.values().toMutableList()
|
||||
types += null
|
||||
|
||||
types.forEach {
|
||||
notificationManager.deleteNotificationChannel(makeChannelId(channelGroupId, it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/colorDrawing"
|
||||
android:pathData="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z"/>
|
||||
</vector>
|
|
@ -47,15 +47,25 @@
|
|||
<!-- Notifications: like (favourite) notification -->
|
||||
<string name="liked_notification">%1$s liked your post</string>
|
||||
|
||||
<!-- Notifications: end of poll notification -->
|
||||
<string name="poll_notification">"%1$s's poll has ended"</string>
|
||||
|
||||
<!-- Notifications: comment notification -->
|
||||
<string name="comment_notification">%1$s commented on your post</string>
|
||||
|
||||
<!-- Notifications: end of poll notification -->
|
||||
<string name="poll_notification">"%1$s's poll has ended"</string>
|
||||
|
||||
<!-- Notifications: other notification -->
|
||||
<string name="other_notification">"Notification from %1$s"</string>
|
||||
|
||||
<string name="followed_notification_channel">"New followers"</string>
|
||||
<string name="mention_notification_channel">"Mentions"</string>
|
||||
<string name="shared_notification_channel">"Shares"</string>
|
||||
<string name="liked_notification_channel">"Likes"</string>
|
||||
<string name="comment_notification_channel">"Comments"</string>
|
||||
<string name="poll_notification_channel">"Polls"</string>
|
||||
<string name="other_notification_channel">"Other"</string>
|
||||
|
||||
|
||||
|
||||
<!-- Login page -->
|
||||
<string name="whats_an_instance">"What's an instance?"</string>
|
||||
<string name="whats_an_instance_explanation">"You might be confused by the text field asking for the domain of your 'instance'.
|
||||
|
@ -235,4 +245,7 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
|||
|
||||
<!-- Error message when a selected file can not be found -->
|
||||
<string name="file_not_found">File %1$s was not found</string>
|
||||
<string name="notifications_settings">Notification settings</string>
|
||||
<string name="notifications_settings_summary">Manage what notifications you want to receive</string>
|
||||
<string name="login_notifications">Couldn\'t fetch latest notifications</string>
|
||||
</resources>
|
|
@ -21,6 +21,16 @@
|
|||
app:useSimpleSummaryProvider="true"
|
||||
app:icon="@drawable/translate_black_24dp" />
|
||||
|
||||
<Preference android:title="@string/notifications_settings"
|
||||
android:key="notification"
|
||||
android:summary="@string/notifications_settings_summary"
|
||||
app:icon="@drawable/ic_baseline_notifications_active_24">
|
||||
<intent android:action="android.settings.APP_NOTIFICATION_SETTINGS">
|
||||
<extra android:name="android.provider.extra.APP_PACKAGE"
|
||||
android:value="@string/application_id" />
|
||||
</intent>
|
||||
</Preference>
|
||||
|
||||
<Preference android:title="@string/about"
|
||||
android:key="about"
|
||||
android:summary="@string/about_pixeldroid"
|
||||
|
|
|
@ -8,58 +8,15 @@ import kotlinx.coroutines.runBlocking
|
|||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
* Unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class APIUnitTest {
|
||||
private val referenceFirstStatus = Status(id="140364967936397312", uri="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
created_at= SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.Z").parse("2020-03-03T08:00:16.+0000"),
|
||||
account=Account(id="115114166443970560", username="Miike", acct="Miike",
|
||||
url="https://pixelfed.de/Miike", display_name="Miike Duart", note="",
|
||||
avatar="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
avatar_static="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
header="", header_static="", locked=false, emojis= emptyList(), discoverable=null,
|
||||
created_at="2019-12-24T15:42:35.000000Z", statuses_count=71, followers_count=14,
|
||||
following_count=0, moved=null, fields=null, bot=false, source=null),
|
||||
content="""Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""",
|
||||
visibility=Status.Visibility.public, sensitive=false, spoiler_text="",
|
||||
media_attachments= listOf(Attachment(id="15888", type= Attachment.AttachmentType.image, url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg",
|
||||
preview_url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg",
|
||||
remote_url=null, text_url=null, description=null, blurhash=null, meta = null)),
|
||||
application= Application(name="web", website=null, vapid_key=null), mentions=emptyList(),
|
||||
tags= listOf(Tag(name="hiking", url="https://pixelfed.de/discover/tags/hiking", history=null), Tag(name="nature", url="https://pixelfed.de/discover/tags/nature", history=null), Tag(name="rotavicentina", url="https://pixelfed.de/discover/tags/rotavicentina", history=null)),
|
||||
emojis= emptyList(), reblogs_count=0, favourites_count=0, replies_count=0, url="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
in_reply_to_id=null, in_reply_to_account=null, reblog=null, poll=null, card=null, language=null, text=null, favourited=null, reblogged=null, muted=null, bookmarked=null, pinned=null)
|
||||
val sampleNotification = Notification("45723", Notification.NotificationType.favourite,
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+hh:mm").parse("2020-03-14T15:01:49+00:00")!!,
|
||||
Account("79574199701737472", "Spaziergaenger",
|
||||
"Spaziergaenger", "https://pixelfed.de/Spaziergaenger",
|
||||
"anonymous", "", "https://pixelfed.de/storage/avatars/007/957/419/970/173/747/2/KEg4YgCgsmzdgyVztszz_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
"https://pixelfed.de/storage/avatars/007/957/419/970/173/747/2/KEg4YgCgsmzdgyVztszz_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
locked=false, followers_count = 40, following_count = 0, statuses_count = 891, created_at = "1568728767", header = "", discoverable = true, emojis = emptyList(), header_static = ""),
|
||||
Status("144456497894658048","https://pixelfed.de/p/dante/144456497894658048",
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'").parse("2020-03-03T08:00:16.000000Z"), in_reply_to_id = null,
|
||||
in_reply_to_account = null, reblog = null,content = "Saturn V launch", emojis = emptyList(), reblogs_count = 0,
|
||||
favourites_count = 1, reblogged = false, favourited = false, muted = false, sensitive = false,
|
||||
spoiler_text = "", visibility = Status.Visibility.public, application = Application("web", null),
|
||||
language = null, pinned = false, mentions = emptyList(), tags = emptyList(), replies_count = 0,
|
||||
account = Account("136453537340198912", "dante", "dante", locked = false, following_count = 3,
|
||||
followers_count = 1,statuses_count = 1, note = "", url = "https://pixelfed.de/dante",
|
||||
avatar = "https://pixelfed.de/storage/avatars/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9",
|
||||
emojis = emptyList(), header_static = "", header = "", created_at = "1582289858", avatar_static = "https://pixelfed.de/storage/avatars/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9",
|
||||
discoverable = true, display_name = "Dante"), media_attachments = listOf(
|
||||
Attachment("16583",Attachment.AttachmentType.image, "https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/0fa8bbe19cc23442034913a7c97fbe4527c1d63a/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad.jpeg",
|
||||
"https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/0fa8bbe19cc23442034913a7c97fbe4527c1d63a/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad_thumb.jpeg",
|
||||
null, null, null, null)
|
||||
)
|
||||
, bookmarked = false, card = null, poll = null, text= null,url= "https://pixelfed.de/p/dante/144456497894658048")
|
||||
, user_id = "", instance_uri = ""
|
||||
)
|
||||
@get:Rule
|
||||
var wireMockRule = WireMockRule(8089)
|
||||
|
||||
|
@ -163,12 +120,12 @@ fun assertStatusEqualsToReference(actual: Status){
|
|||
assert(
|
||||
((actual.id=="140364967936397312"
|
||||
&& actual.uri=="https://pixelfed.de/p/Miike/140364967936397312"
|
||||
&& actual.created_at==SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.Z").parse("2020-03-03T08:00:16.+0000")
|
||||
&& actual.created_at == Instant.parse("2020-03-03T08:00:16.+00:00")
|
||||
&& actual.account!!.id=="115114166443970560"&& actual.account!!.username=="Miike"&& actual.account!!.acct=="Miike" &&
|
||||
actual.account!!.url=="https://pixelfed.de/Miike"&& actual.account!!.display_name=="Miike Duart"&& actual.account!!.note==""&&
|
||||
//actual.account!!.avatar=="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35"&&
|
||||
//actual.account!!.avatar_static=="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35"&&
|
||||
actual.account!!.header==""&& actual.account!!.header_static=="") && !actual.account!!.locked!! && actual.account!!.emojis== emptyList<Emoji>() && actual.account!!.discoverable == null && actual.account!!.created_at=="2019-12-24T15:42:35.000000Z" && actual.account!!.statuses_count==71 && actual.account!!.followers_count==14 && actual.account!!.following_count==0 && actual.account!!.moved==null && actual.account!!.fields==null && !actual.account!!.bot!! && actual.account!!.source==null && actual.content == """Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""" && actual.visibility==Status.Visibility.public) && !actual.sensitive!! && actual.spoiler_text==""
|
||||
actual.account!!.header==""&& actual.account!!.header_static=="") && !actual.account!!.locked!! && actual.account!!.emojis== emptyList<Emoji>() && actual.account!!.discoverable == null && actual.account!!.created_at==Instant.parse("2019-12-24T15:42:35.000000Z") && actual.account!!.statuses_count==71 && actual.account!!.followers_count==14 && actual.account!!.following_count==0 && actual.account!!.moved==null && actual.account!!.fields==null && !actual.account!!.bot!! && actual.account!!.source==null && actual.content == """Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""" && actual.visibility==Status.Visibility.public) && !actual.sensitive!! && actual.spoiler_text==""
|
||||
)
|
||||
val attchmnt = actual.media_attachments!![0]
|
||||
assert(attchmnt.id == "15888" && attchmnt.type == Attachment.AttachmentType.image && attchmnt.url=="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg" &&
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package org.pixeldroid.app
|
||||
|
||||
import org.pixeldroid.app.utils.api.objects.*
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.text.SimpleDateFormat
|
||||
import org.pixeldroid.app.utils.api.objects.*
|
||||
import java.time.Instant
|
||||
|
||||
class PostUnitTest {
|
||||
private val status = Status(id="140364967936397312", uri="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
created_at= SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'").parse("2020-03-03T08:00:16.000000Z"),
|
||||
created_at= Instant.parse("2020-03-03T08:00:16+00:00"),
|
||||
account= Account(id="115114166443970560", username="Miike", acct="Miike",
|
||||
url="https://pixelfed.de/Miike", display_name="Miike Duart", note="",
|
||||
avatar="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
avatar_static="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
header="", header_static="", locked=false, emojis= emptyList(), discoverable=false,
|
||||
created_at="2019-12-24T15:42:35.000000Z", statuses_count=71, followers_count=14,
|
||||
created_at=Instant.parse("2019-12-24T15:42:35.000000Z"), statuses_count=71, followers_count=14,
|
||||
following_count=0, moved=null, fields=null, bot=false, source=null),
|
||||
content="""Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""",
|
||||
visibility=Status.Visibility.public, sensitive=false, spoiler_text="",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#Tue Apr 20 12:38:34 CEST 2021
|
||||
#Sat Sep 18 21:24:34 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||
distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
distributionSha256Sum=f581709a9c35e9cb92e16f585d2c4bc99b2b1a5f85d2badbd3dc6bff59e1e6dd
|
||||
|
|
Loading…
Reference in New Issue