From 61cdb1118b7e7abf4909757531e9ba8e8c7c0859 Mon Sep 17 00:00:00 2001 From: Wv5twkFEKh54vo4tta9yu7dHa3 <61561059+Wv5twkFEKh54vo4tta9yu7dHa3@users.noreply.github.com> Date: Wed, 1 Apr 2020 19:38:21 +0200 Subject: [PATCH] WIP: Loading (#76) * Add loading indications, refactor fragments into common parent class * Add cirrus * actually add cirrus * Add timeouts * Fix test rules * Add gitlab ci file to tes * move tests to MockedServer to not have infinite waits by Espresso * Update README for gitlab badge --- .cirrus.yml | 34 +++++++ .gitlab-ci.yml | 34 +++++++ .travis.yml | 29 ------ README.md | 2 +- android-wait-for-emulator.sh | 26 +++++ app/build.gradle | 5 + .../java/com/h/pixeldroid/AppDatabaseTest.kt | 9 +- .../h/pixeldroid/ExampleInstrumentedTest.kt | 22 ----- .../com/h/pixeldroid/LoginInstrumentedTest.kt | 7 ++ .../java/com/h/pixeldroid/MockedServerTest.kt | 45 +++++++++ .../java/com/h/pixeldroid/SettingsTest.kt | 49 --------- .../java/com/h/pixeldroid/SwipeTest.kt | 63 ------------ .../java/com/h/pixeldroid/LoginActivity.kt | 27 ++++- .../java/com/h/pixeldroid/MainActivity.kt | 9 +- .../java/com/h/pixeldroid/PostActivity.kt | 8 +- .../h/pixeldroid/fragments/HomeFragment.kt | 78 --------------- .../fragments/NotificationsFragment.kt | 99 ------------------- .../h/pixeldroid/fragments/PostFragment.kt | 8 +- .../fragments/feeds/FeedFragment.kt | 98 ++++++++++++++++++ .../fragments/feeds/HomeFragment.kt | 34 +++++++ .../HomeRecyclerViewAdapter.kt} | 28 ++---- .../fragments/feeds/NotificationsFragment.kt | 44 +++++++++ .../NotificationsRecyclerViewAdapter.kt | 25 ++--- .../main/java/com/h/pixeldroid/models/Post.kt | 86 ---------------- .../java/com/h/pixeldroid/objects/Status.kt | 77 +++++++++++++++ .../com/h/pixeldroid/utils/ImageConverter.kt | 1 - app/src/main/res/layout/activity_login.xml | 76 +++++++++----- app/src/main/res/layout/fragment_feed.xml | 27 +++++ app/src/main/res/layout/fragment_home.xml | 18 ++-- .../layout/fragment_notifications_list.xml | 21 ---- .../java/com/h/pixeldroid/PostUnitTest.kt | 22 ++--- gradle.properties | 3 + 32 files changed, 563 insertions(+), 551 deletions(-) create mode 100644 .cirrus.yml create mode 100644 .gitlab-ci.yml delete mode 100644 .travis.yml create mode 100644 android-wait-for-emulator.sh delete mode 100644 app/src/androidTest/java/com/h/pixeldroid/ExampleInstrumentedTest.kt delete mode 100644 app/src/androidTest/java/com/h/pixeldroid/SettingsTest.kt delete mode 100644 app/src/androidTest/java/com/h/pixeldroid/SwipeTest.kt delete mode 100644 app/src/main/java/com/h/pixeldroid/fragments/HomeFragment.kt delete mode 100644 app/src/main/java/com/h/pixeldroid/fragments/NotificationsFragment.kt create mode 100644 app/src/main/java/com/h/pixeldroid/fragments/feeds/FeedFragment.kt create mode 100644 app/src/main/java/com/h/pixeldroid/fragments/feeds/HomeFragment.kt rename app/src/main/java/com/h/pixeldroid/fragments/{FeedRecyclerViewAdapter.kt => feeds/HomeRecyclerViewAdapter.kt} (77%) create mode 100644 app/src/main/java/com/h/pixeldroid/fragments/feeds/NotificationsFragment.kt rename app/src/main/java/com/h/pixeldroid/fragments/{ => feeds}/NotificationsRecyclerViewAdapter.kt (85%) delete mode 100644 app/src/main/java/com/h/pixeldroid/models/Post.kt create mode 100644 app/src/main/res/layout/fragment_feed.xml delete mode 100644 app/src/main/res/layout/fragment_notifications_list.xml diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..7f145d11 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,34 @@ +connected_check_task: + name: Run Android instrumented tests + env: + API_LEVEL: 23 + TARGET: default + ARCH: x86 + CC_TEST_REPORTER_ID: ENCRYPTED[!b71004b17b92e8fb7a3fecc3e2a9cc28c4e5f07f55e2f20cdfc641c57487cd21c7df6e7930318f8d87bc4675e63b260d!] + container: + image: reactivecircus/android-emulator-23:latest + kvm: true + cpu: 8 + memory: 16G + create_device_script: + echo no | avdmanager create avd --force --name "api-${API_LEVEL}" --abi "${TARGET}/${ARCH}" --package "system-images;android-${API_LEVEL};${TARGET};${ARCH}" + start_emulator_background_script: + $ANDROID_HOME/emulator/emulator -avd "api-${API_LEVEL}" -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none + wait_for_emulator_script: + - chmod +x android-wait-for-emulator.sh + - ./android-wait-for-emulator.sh + disable_animations_script: | + adb shell settings put global window_animation_scale 0.0 + adb shell settings put global transition_animation_scale 0.0 + adb shell settings put global animator_duration_scale 0.0 + prepare_codeclimate_script: + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build + run_instrumented_tests_script: + ./gradlew build connectedCheck jacocoTestReport + report_codeclimate_script: + # Report test coverage to Code Climate + - export JACOCO_SOURCE_PATH=app/src/main/java/ + - ./cc-test-reporter format-coverage ./app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml --input-type jacoco + - ./cc-test-reporter upload-coverage diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..cc26d9f1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,34 @@ +image: reactivecircus/android-emulator-23:latest + +variables: + API_LEVEL: "23" + API_LEVEL: "23" + ARCH: "x86" + TARGET: "default" + #CC_TEST_REPORTER_ID defined in GitLab CI settings + +stages: + - test + - report + +test: + stage: test + script: + - echo no | avdmanager create avd --force --name "api-${API_LEVEL}" --abi "${TARGET}/${ARCH}" --package "system-images;android-${API_LEVEL};${TARGET};${ARCH}" + - $ANDROID_HOME/emulator/emulator -avd "api-${API_LEVEL}" -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none & + - chmod +x android-wait-for-emulator.sh + - ./android-wait-for-emulator.sh + - adb shell settings put global window_animation_scale 0.0 + - adb shell settings put global transition_animation_scale 0.0 + - adb shell settings put global animator_duration_scale 0.0 + + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build + + - ./gradlew build connectedCheck jacocoTestReport + + # Report test coverage to Code Climate + - export JACOCO_SOURCE_PATH=app/src/main/java/ + - ./cc-test-reporter format-coverage ./app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml --input-type jacoco + - ./cc-test-reporter upload-coverage diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0f4df905..00000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -language: android -android: - components: - # The BuildTools version used by your project (make sure it's exactly the same as in the build.gradle) - - build-tools-29.0.3 - # The SDK version used to compile your project - - android-29 - # The SDK version used by the system image - - android-23 - # The system image, to run an emulator during the tests - - sys-img-armeabi-v7a-android-23 -before_script: - # Emulator Management: Create, Start and Wait - - echo no | android create avd --force -n test -t android-23 --abi armeabi-v7a - - export QEMU_AUDIO_DRV=none && emulator -avd test -no-window & - - android-wait-for-emulator - - adb shell input keyevent 82 - # This should be in the `before_script` entry - # Set up Code Climate test reporter - - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - - chmod +x ./cc-test-reporter - - ./cc-test-reporter before-build -script: - - ./gradlew build connectedCheck jacocoTestReport -after_script: - # Report test coverage to Code Climate - - export JACOCO_SOURCE_PATH=app/src/main/java/ - - ./cc-test-reporter format-coverage ./app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml --input-type jacoco - - ./cc-test-reporter upload-coverage diff --git a/README.md b/README.md index 78a41059..4d145b1d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # PixelDroid Software Development Project course, EPFL, Spring 2020 -[![Build Status](https://travis-ci.org/H-PixelDroid/PixelDroid.svg?branch=master)](https://travis-ci.org/H-PixelDroid/PixelDroid) [![Maintainability](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/maintainability)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/test_coverage)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/test_coverage) +[![Build Status](https://gitlab.com/Matttter/PixelDroid/badges/master/pipeline.svg)](https://gitlab.com/Matttter/PixelDroid/pipelines) [![Maintainability](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/maintainability)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/test_coverage)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/test_coverage) diff --git a/android-wait-for-emulator.sh b/android-wait-for-emulator.sh new file mode 100644 index 00000000..e41d628d --- /dev/null +++ b/android-wait-for-emulator.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Originally written by Ralf Kistner , but placed in the public domain + +#set -x +set +e + +bootanim="" +failcounter=0 +timeout_in_sec=600 # 10 minutes + +until [[ "$bootanim" =~ "stopped" ]]; do + bootanim=`adb -e shell getprop init.svc.bootanim 2>&1 &` + if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline" + || "$bootanim" =~ "running" || "$bootanim" =~ "error: no emulators found" ]]; then + let "failcounter += 1" + echo "Waiting for emulator to start: $failcounter of $timeout_in_sec : status: $bootanim" + if [[ $failcounter -ge timeout_in_sec ]]; then + echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator" + exit 1 + fi + fi + sleep 1 +done + +echo "Emulator is ready" diff --git a/app/build.gradle b/app/build.gradle index bda03189..ccdb0302 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,6 +76,11 @@ dependencies { exclude group: "com.android.support" } implementation "com.github.bumptech.glide:okhttp-integration:4.11.0" + implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { + // Excludes the support library because it's already included by Glide. + transitive = false + } + annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation "com.github.tomakehurst:wiremock-jre8:2.26.3" diff --git a/app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt b/app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt index fcfd86c6..0e079c16 100644 --- a/app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt +++ b/app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt @@ -6,10 +6,8 @@ import com.h.pixeldroid.db.AppDatabase import com.h.pixeldroid.db.PostDao import com.h.pixeldroid.db.PostEntity import com.h.pixeldroid.utils.* -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test +import org.junit.* +import org.junit.rules.Timeout import org.junit.runner.RunWith import java.util.Calendar @@ -18,7 +16,8 @@ class AppDatabaseTest { private var postDao: PostDao? = null private var db: AppDatabase? = null private var postTest = PostEntity(1, "test", date= Calendar.getInstance().time) - + @get:Rule + var globalTimeout: Timeout = Timeout.seconds(100) @Before fun setup() { AppDatabase.TEST_MODE = true diff --git a/app/src/androidTest/java/com/h/pixeldroid/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/h/pixeldroid/ExampleInstrumentedTest.kt deleted file mode 100644 index 9f8c3197..00000000 --- a/app/src/androidTest/java/com/h/pixeldroid/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.h.pixeldroid - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.h.pixeldroid", appContext.packageName) - } -} diff --git a/app/src/androidTest/java/com/h/pixeldroid/LoginInstrumentedTest.kt b/app/src/androidTest/java/com/h/pixeldroid/LoginInstrumentedTest.kt index 6f6b3a3e..28fb45f5 100644 --- a/app/src/androidTest/java/com/h/pixeldroid/LoginInstrumentedTest.kt +++ b/app/src/androidTest/java/com/h/pixeldroid/LoginInstrumentedTest.kt @@ -27,6 +27,7 @@ import org.hamcrest.Matcher import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.rules.Timeout import org.junit.runner.RunWith @@ -37,6 +38,8 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class LoginInstrumentedTest { + @get:Rule + var globalTimeout: Timeout = Timeout.seconds(100) @get:Rule var activityRule: ActivityScenarioRule = ActivityScenarioRule(LoginActivity::class.java) @@ -63,6 +66,8 @@ class LoginInstrumentedTest { @RunWith(AndroidJUnit4::class) class LoginCheckIntent { + @get:Rule + var globalTimeout: Timeout = Timeout.seconds(100) @get:Rule val intentsTestRule = IntentsTestRule(LoginActivity::class.java) @@ -85,6 +90,8 @@ class LoginCheckIntent { @RunWith(AndroidJUnit4::class) class AfterIntent { + @get:Rule + var globalTimeout: Timeout = Timeout.seconds(100) @get:Rule val rule = ActivityTestRule(LoginActivity::class.java) diff --git a/app/src/androidTest/java/com/h/pixeldroid/MockedServerTest.kt b/app/src/androidTest/java/com/h/pixeldroid/MockedServerTest.kt index 44acc66d..fa41bb59 100644 --- a/app/src/androidTest/java/com/h/pixeldroid/MockedServerTest.kt +++ b/app/src/androidTest/java/com/h/pixeldroid/MockedServerTest.kt @@ -1,10 +1,15 @@ package com.h.pixeldroid import android.content.Context +import android.view.Gravity import androidx.test.core.app.ActivityScenario import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.DrawerActions +import androidx.test.espresso.contrib.DrawerMatchers +import androidx.test.espresso.contrib.NavigationViewActions +import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.ActivityScenarioRule @@ -18,6 +23,7 @@ import okhttp3.mockwebserver.RecordedRequest import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.rules.Timeout import org.junit.runner.RunWith @@ -49,6 +55,8 @@ class MockedServerTest { private val feedJson = """[{"id":"140364967936397312","uri":"https:\/\/pixelfed.de\/p\/Miike\/140364967936397312","url":"https:\/\/pixelfed.de\/p\/Miike\/140364967936397312","in_reply_to_id":null,"in_reply_to_account_id":null,"reblog":null,"content":"Day 8 #rotavicentina<\/a> #hiking<\/a> #nature<\/a>","created_at":"2020-03-03T08:00:16.000000Z","emojis":[],"replies_count":0,"reblogs_count":0,"favourites_count":0,"reblogged":null,"favourited":null,"muted":null,"sensitive":false,"spoiler_text":"","visibility":"public","mentions":[],"tags":[{"name":"hiking","url":"https:\/\/pixelfed.de\/discover\/tags\/hiking"},{"name":"nature","url":"https:\/\/pixelfed.de\/discover\/tags\/nature"},{"name":"rotavicentina","url":"https:\/\/pixelfed.de\/discover\/tags\/rotavicentina"}],"card":null,"poll":null,"application":{"name":"web","website":null},"language":null,"pinned":null,"account":{"id":"115114166443970560","username":"Miike","acct":"Miike","display_name":"Miike Duart","locked":false,"created_at":"2019-12-24T15:42:35.000000Z","followers_count":14,"following_count":0,"statuses_count":71,"note":"","url":"https:\/\/pixelfed.de\/Miike","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":"","emojis":[],"moved":null,"fields":null,"bot":false,"software":"pixelfed","is_admin":false},"media_attachments":[{"id":"15888","type":"image","url":"https:\/\/pixelfed.de\/storage\/m\/113a3e2124a33b1f5511e531953f5ee48456e0c7\/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f\/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg","remote_url":null,"preview_url":"https:\/\/pixelfed.de\/storage\/m\/113a3e2124a33b1f5511e531953f5ee48456e0c7\/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f\/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg","text_url":null,"meta":null,"description":null}]},{"id":"140349785193451520","uri":"https:\/\/pixelfed.de\/p\/stephan\/140349785193451520","url":"https:\/\/pixelfed.de\/p\/stephan\/140349785193451520","in_reply_to_id":null,"in_reply_to_account_id":null,"reblog":null,"content":"","created_at":"2020-03-03T06:59:56.000000Z","emojis":[],"replies_count":0,"reblogs_count":0,"favourites_count":2,"reblogged":null,"favourited":null,"muted":null,"sensitive":false,"spoiler_text":"","visibility":"public","mentions":[],"tags":[],"card":null,"poll":null,"application":{"name":"web","website":null},"language":null,"pinned":null,"account":{"id":"908","username":"stephan","acct":"stephan","display_name":"Stephan","locked":false,"created_at":"2019-03-17T07:46:33.000000Z","followers_count":136,"following_count":25,"statuses_count":136,"note":"Musician, software developer & hobby photographer.","url":"https:\/\/pixelfed.de\/stephan","avatar":"https:\/\/pixelfed.de\/storage\/avatars\/000\/000\/000\/908\/5nQzzsB1mkwKaUqQ9GNN_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","avatar_static":"https:\/\/pixelfed.de\/storage\/avatars\/000\/000\/000\/908\/5nQzzsB1mkwKaUqQ9GNN_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","header":"","header_static":"","emojis":[],"moved":null,"fields":null,"bot":false,"software":"pixelfed","is_admin":false},"media_attachments":[{"id":"15887","type":"image","url":"https:\/\/pixelfed.de\/storage\/m\/113a3e2124a33b1f5511e531953f5ee48456e0c7\/a1349f5183c2bac7d52880e8f5188df0f3b2d62a\/mvT3nYV6Wdu42Xh56Ny4VYaWU0OzbnC3wjxiqnKS.jpeg","remote_url":null,"preview_url":"https:\/\/pixelfed.de\/storage\/m\/113a3e2124a33b1f5511e531953f5ee48456e0c7\/a1349f5183c2bac7d52880e8f5188df0f3b2d62a\/mvT3nYV6Wdu42Xh56Ny4VYaWU0OzbnC3wjxiqnKS_thumb.jpeg","text_url":null,"meta":null,"description":null}]},{"id":"140276879742603264","uri":"https:\/\/pixelfed.de\/p\/fegrimaldi\/140276879742603264","url":"https:\/\/pixelfed.de\/p\/fegrimaldi\/140276879742603264","in_reply_to_id":null,"in_reply_to_account_id":null,"reblog":null,"content":"february 2 is the day to give flowers to Iemanj\u00e1. #salvador<\/a> #bahia<\/a> #brazil<\/a> #iemanja<\/a>","created_at":"2020-03-03T02:10:14.000000Z","emojis":[],"replies_count":0,"reblogs_count":0,"favourites_count":1,"reblogged":null,"favourited":null,"muted":null,"sensitive":false,"spoiler_text":"","visibility":"public","mentions":[],"tags":[{"name":"salvador","url":"https:\/\/pixelfed.de\/discover\/tags\/salvador"},{"name":"bahia","url":"https:\/\/pixelfed.de\/discover\/tags\/bahia"},{"name":"brazil","url":"https:\/\/pixelfed.de\/discover\/tags\/brazil"},{"name":"iemanja","url":"https:\/\/pixelfed.de\/discover\/tags\/iemanja"}],"card":null,"poll":null,"application":{"name":"web","website":null},"language":null,"pinned":null,"account":{"id":"137257212828585984","username":"fegrimaldi","acct":"fegrimaldi","display_name":"Fernanda Grimaldi","locked":false,"created_at":"2020-02-23T18:11:09.000000Z","followers_count":2,"following_count":7,"statuses_count":2,"note":"a little piece of Bahia in the fediverse.","url":"https:\/\/pixelfed.de\/fegrimaldi","avatar":"https:\/\/pixelfed.de\/storage\/avatars\/013\/725\/721\/282\/858\/598\/4\/oUPBit0TJso1xNhJfFqg_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","avatar_static":"https:\/\/pixelfed.de\/storage\/avatars\/013\/725\/721\/282\/858\/598\/4\/oUPBit0TJso1xNhJfFqg_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","header":"","header_static":"","emojis":[],"moved":null,"fields":null,"bot":false,"software":"pixelfed","is_admin":false},"media_attachments":[{"id":"15886","type":"image","url":"https:\/\/pixelfed.de\/storage\/m\/113a3e2124a33b1f5511e531953f5ee48456e0c7\/feb878b4bd60b85ac840670c6b9c809fd76b628b\/lYMrx0WF8LDqn0vTRgNJaRs7stMKtAXrgzpMrWEr.jpeg","remote_url":null,"preview_url":"https:\/\/pixelfed.de\/storage\/m\/113a3e2124a33b1f5511e531953f5ee48456e0c7\/feb878b4bd60b85ac840670c6b9c809fd76b628b\/lYMrx0WF8LDqn0vTRgNJaRs7stMKtAXrgzpMrWEr_thumb.jpeg","text_url":null,"meta":null,"description":null}]}]""" private val notificationsJson = "[{\"id\":\"45945\",\"type\":\"favourite\",\"created_at\":\"2020-03-15T14:49:20.000000Z\",\"account\":{\"id\":\"136800034732773376\",\"username\":\"Dobios\",\"acct\":\"Dobios\",\"display_name\":\"Andrew Dobis\",\"locked\":false,\"created_at\":\"2020-02-22T11:54:29.000000Z\",\"followers_count\":2,\"following_count\":1,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Dobios\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144456497894658048\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"reblog\":null,\"content\":\"Saturn V launch\",\"created_at\":\"2020-03-14T14:58:32.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":6,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"136453537340198912\",\"username\":\"Geonosys\",\"acct\":\"dante\",\"display_name\":\"Dante\",\"locked\":false,\"created_at\":\"2020-02-21T12:57:38.000000Z\",\"followers_count\":3,\"following_count\":4,\"statuses_count\":1,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/dante\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"media_attachments\":[{\"id\":\"16583\",\"type\":\"image\",\"url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad.jpeg\",\"remote_url\":null,\"preview_url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad_thumb.jpeg\",\"text_url\":null,\"meta\":null,\"description\":null}]}},{\"id\":\"45944\",\"type\":\"follow\",\"created_at\":\"2020-03-15T14:49:11.000000Z\",\"account\":{\"id\":\"136800034732773376\",\"username\":\"Dobios\",\"acct\":\"Dobios\",\"display_name\":\"Andrew Dobis\",\"locked\":false,\"created_at\":\"2020-02-22T11:54:29.000000Z\",\"followers_count\":2,\"following_count\":1,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Dobios\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false}},{\"id\":\"45942\",\"type\":\"reblog\",\"created_at\":\"2020-03-15T14:41:04.000000Z\",\"account\":{\"id\":\"144813993922531328\",\"username\":\"Clement\",\"acct\":\"Clement\",\"display_name\":\"Andrea\",\"locked\":false,\"created_at\":\"2020-03-15T14:39:06.000000Z\",\"followers_count\":0,\"following_count\":2,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Clement\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144814478708576256\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/Clement\\/144814478708576256\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/Clement\\/144814478708576256\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":136453537340198912,\"reblog\":null,\"content\":\"\",\"created_at\":\"2020-03-15T14:41:02.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"144813993922531328\",\"username\":\"Clement\",\"acct\":\"Clement\",\"display_name\":\"Andrea\",\"locked\":false,\"created_at\":\"2020-03-15T14:39:06.000000Z\",\"followers_count\":0,\"following_count\":2,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Clement\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false}}},{\"id\":\"45941\",\"type\":\"mention\",\"created_at\":\"2020-03-15T14:40:52.000000Z\",\"account\":{\"id\":\"144813993922531328\",\"username\":\"Clement\",\"acct\":\"Clement\",\"display_name\":\"Andrea\",\"locked\":false,\"created_at\":\"2020-03-15T14:39:06.000000Z\",\"followers_count\":0,\"following_count\":2,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Clement\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144814428691501056\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/Clement\\/144814428691501056\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/Clement\\/144814428691501056\",\"in_reply_to_id\":144456497894658048,\"in_reply_to_account_id\":136453537340198912,\"reblog\":null,\"content\":\"@dante<\\/a> I identify to this pic.\",\"created_at\":\"2020-03-15T14:40:50.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":1,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[{\"id\":\"136453537340198912\",\"url\":\"https:\\/\\/pixelfed.de\\/dante\",\"username\":\"dante\",\"acct\":\"dante\"}],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"144813993922531328\",\"username\":\"Clement\",\"acct\":\"Clement\",\"display_name\":\"Andrea\",\"locked\":false,\"created_at\":\"2020-03-15T14:39:06.000000Z\",\"followers_count\":0,\"following_count\":2,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Clement\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false}}},{\"id\":\"45940\",\"type\":\"favourite\",\"created_at\":\"2020-03-15T14:40:22.000000Z\",\"account\":{\"id\":\"144813993922531328\",\"username\":\"Clement\",\"acct\":\"Clement\",\"display_name\":\"Andrea\",\"locked\":false,\"created_at\":\"2020-03-15T14:39:06.000000Z\",\"followers_count\":0,\"following_count\":2,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Clement\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144456497894658048\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"reblog\":null,\"content\":\"Saturn V launch\",\"created_at\":\"2020-03-14T14:58:32.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":6,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"136453537340198912\",\"username\":\"dante\",\"acct\":\"dante\",\"display_name\":\"Dante\",\"locked\":false,\"created_at\":\"2020-02-21T12:57:38.000000Z\",\"followers_count\":3,\"following_count\":4,\"statuses_count\":1,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/dante\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"media_attachments\":[{\"id\":\"16583\",\"type\":\"image\",\"url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad.jpeg\",\"remote_url\":null,\"preview_url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad_thumb.jpeg\",\"text_url\":null,\"meta\":null,\"description\":null}]}},{\"id\":\"45939\",\"type\":\"follow\",\"created_at\":\"2020-03-15T14:40:12.000000Z\",\"account\":{\"id\":\"144813993922531328\",\"username\":\"Clement\",\"acct\":\"Clement\",\"display_name\":\"Andrea\",\"locked\":false,\"created_at\":\"2020-03-15T14:39:06.000000Z\",\"followers_count\":0,\"following_count\":2,\"statuses_count\":0,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Clement\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false}},{\"id\":\"45804\",\"type\":\"favourite\",\"created_at\":\"2020-03-15T02:22:47.000000Z\",\"account\":{\"id\":\"131984031779786752\",\"username\":\"joska\",\"acct\":\"joska\",\"display_name\":\"jxzk\",\"locked\":false,\"created_at\":\"2020-02-09T04:57:25.000000Z\",\"followers_count\":3,\"following_count\":2,\"statuses_count\":82,\"note\":\"Feliz :D\",\"url\":\"https:\\/\\/pixelfed.de\\/joska\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/013\\/198\\/403\\/177\\/978\\/675\\/2\\/zhytNrT3ij5cHBXX1mJv_avatar.jpeg?v=4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/013\\/198\\/403\\/177\\/978\\/675\\/2\\/zhytNrT3ij5cHBXX1mJv_avatar.jpeg?v=4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144456497894658048\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"reblog\":null,\"content\":\"Saturn V launch\",\"created_at\":\"2020-03-14T14:58:32.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":6,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"136453537340198912\",\"username\":\"dante\",\"acct\":\"dante\",\"display_name\":\"Dante\",\"locked\":false,\"created_at\":\"2020-02-21T12:57:38.000000Z\",\"followers_count\":3,\"following_count\":4,\"statuses_count\":1,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/dante\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"media_attachments\":[{\"id\":\"16583\",\"type\":\"image\",\"url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad.jpeg\",\"remote_url\":null,\"preview_url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad_thumb.jpeg\",\"text_url\":null,\"meta\":null,\"description\":null}]}},{\"id\":\"45783\",\"type\":\"favourite\",\"created_at\":\"2020-03-15T00:45:53.000000Z\",\"account\":{\"id\":\"139939422090170368\",\"username\":\"DrMsch\",\"acct\":\"DrMsch\",\"display_name\":\"Mische\",\"locked\":false,\"created_at\":\"2020-03-02T03:49:18.000000Z\",\"followers_count\":13,\"following_count\":21,\"statuses_count\":9,\"note\":\"TelefonFotos und Malereien von DerMische\",\"url\":\"https:\\/\\/pixelfed.de\\/DrMsch\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/013\\/993\\/942\\/209\\/017\\/036\\/8\\/xQUOq3tBNgOhFItKMZ56_avatar.jpeg?v=4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/013\\/993\\/942\\/209\\/017\\/036\\/8\\/xQUOq3tBNgOhFItKMZ56_avatar.jpeg?v=4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144456497894658048\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"reblog\":null,\"content\":\"Saturn V launch\",\"created_at\":\"2020-03-14T14:58:32.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":6,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"136453537340198912\",\"username\":\"dante\",\"acct\":\"dante\",\"display_name\":\"Dante\",\"locked\":false,\"created_at\":\"2020-02-21T12:57:38.000000Z\",\"followers_count\":3,\"following_count\":4,\"statuses_count\":1,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/dante\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"media_attachments\":[{\"id\":\"16583\",\"type\":\"image\",\"url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad.jpeg\",\"remote_url\":null,\"preview_url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad_thumb.jpeg\",\"text_url\":null,\"meta\":null,\"description\":null}]}},{\"id\":\"45768\",\"type\":\"favourite\",\"created_at\":\"2020-03-14T22:43:18.000000Z\",\"account\":{\"id\":\"139819612522024960\",\"username\":\"vitorpires\",\"acct\":\"vitorpires\",\"display_name\":\"Vitor Pires\",\"locked\":false,\"created_at\":\"2020-03-01T19:53:13.000000Z\",\"followers_count\":20,\"following_count\":8,\"statuses_count\":42,\"note\":\"photography\\/graphic design\\/desktop publishing\\/illustration\\/3D animation\\/video\\/content writing\\/teaching\\/woodworking\\/luthier\",\"url\":\"https:\\/\\/pixelfed.de\\/vitorpires\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/013\\/981\\/961\\/252\\/202\\/496\\/0\\/2HB6Gs2m5NaSys7W5ikG_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/013\\/981\\/961\\/252\\/202\\/496\\/0\\/2HB6Gs2m5NaSys7W5ikG_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144456497894658048\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"reblog\":null,\"content\":\"Saturn V launch\",\"created_at\":\"2020-03-14T14:58:32.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":6,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"136453537340198912\",\"username\":\"dante\",\"acct\":\"dante\",\"display_name\":\"Dante\",\"locked\":false,\"created_at\":\"2020-02-21T12:57:38.000000Z\",\"followers_count\":3,\"following_count\":4,\"statuses_count\":1,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/dante\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"media_attachments\":[{\"id\":\"16583\",\"type\":\"image\",\"url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad.jpeg\",\"remote_url\":null,\"preview_url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad_thumb.jpeg\",\"text_url\":null,\"meta\":null,\"description\":null}]}},{\"id\":\"45723\",\"type\":\"favourite\",\"created_at\":\"2020-03-14T15:01:49.000000Z\",\"account\":{\"id\":\"79574199701737472\",\"username\":\"Spaziergaenger\",\"acct\":\"Spaziergaenger\",\"display_name\":\"anonymous\",\"locked\":false,\"created_at\":\"2019-09-17T13:59:27.000000Z\",\"followers_count\":40,\"following_count\":0,\"statuses_count\":894,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/Spaziergaenger\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/007\\/957\\/419\\/970\\/173\\/747\\/2\\/KEg4YgCgsmzdgyVztszz_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/007\\/957\\/419\\/970\\/173\\/747\\/2\\/KEg4YgCgsmzdgyVztszz_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"status\":{\"id\":\"144456497894658048\",\"uri\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"url\":\"https:\\/\\/pixelfed.de\\/p\\/dante\\/144456497894658048\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"reblog\":null,\"content\":\"Saturn V launch\",\"created_at\":\"2020-03-14T14:58:32.000000Z\",\"emojis\":[],\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":6,\"reblogged\":null,\"favourited\":null,\"muted\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"mentions\":[],\"tags\":[],\"card\":null,\"poll\":null,\"application\":{\"name\":\"web\",\"website\":null},\"language\":null,\"pinned\":null,\"account\":{\"id\":\"136453537340198912\",\"username\":\"dante\",\"acct\":\"dante\",\"display_name\":\"Dante\",\"locked\":false,\"created_at\":\"2020-02-21T12:57:38.000000Z\",\"followers_count\":3,\"following_count\":4,\"statuses_count\":1,\"note\":\"\",\"url\":\"https:\\/\\/pixelfed.de\\/dante\",\"avatar\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"avatar_static\":\"https:\\/\\/pixelfed.de\\/storage\\/avatars\\/default.png?v=5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\",\"header\":\"\",\"header_static\":\"\",\"emojis\":[],\"moved\":null,\"fields\":null,\"bot\":false,\"software\":\"pixelfed\",\"is_admin\":false},\"media_attachments\":[{\"id\":\"16583\",\"type\":\"image\",\"url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad.jpeg\",\"remote_url\":null,\"preview_url\":\"https:\\/\\/pixelfed.de\\/storage\\/m\\/113a3e2124a33b1f5511e531953f5ee48456e0c7\\/0fa8bbe19cc23442034913a7c97fbe4527c1d63a\\/vs2vouJ86OvzxhK9ewhPlfPf4Y9IoQ5CHfiBIqad_thumb.jpeg\",\"text_url\":null,\"meta\":null,\"description\":null}]}}]" + @get:Rule + var globalTimeout: Timeout = Timeout.seconds(100) @get:Rule var activityRule: ActivityScenarioRule = ActivityScenarioRule(MainActivity::class.java) @@ -118,5 +126,42 @@ class MockedServerTest { Thread.sleep(1000) onView(withText("Geonosys")).check(matches(withId(R.id.username))) } + @Test + fun testDrawerSettingsButton() { + // Open Drawer to click on navigation. + onView(withId(R.id.drawer_layout)) + .check(matches(DrawerMatchers.isClosed(Gravity.LEFT))) // Left Drawer should be closed. + .perform(DrawerActions.open()); // Open Drawer + // Start the screen of your activity. + onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_settings)) + + // Check that settings activity was opened. + onView(withText(R.string.signature_title)).check(matches(ViewMatchers.isDisplayed())) + } + + @Test + fun swipingLeftStopsAtProfile() { + onView(withId(R.id.main_activity_main_linear_layout)) + .perform(ViewActions.swipeLeft()) // search + .perform(ViewActions.swipeLeft()) // camera + .perform(ViewActions.swipeLeft()) // notifications + .perform(ViewActions.swipeLeft()) // profile + .perform(ViewActions.swipeLeft()) // should stop at profile + onView(withId(R.id.nbFollowersTextView)).check(matches(ViewMatchers.isDisplayed())) + } + + @Test + fun swipingRightStopsAtHomepage() { + ActivityScenario.launch(MainActivity::class.java).onActivity { + a -> a.findViewById(R.id.tabs).getTabAt(4)?.select() + } // go to the last tab + onView(withId(R.id.main_activity_main_linear_layout)) + .perform(ViewActions.swipeRight()) // notifications + .perform(ViewActions.swipeRight()) // camera + .perform(ViewActions.swipeRight()) // search + .perform(ViewActions.swipeRight()) // homepage + .perform(ViewActions.swipeRight()) // should stop at homepage + onView(withId(R.id.list)).check(matches(ViewMatchers.isDisplayed())) + } } diff --git a/app/src/androidTest/java/com/h/pixeldroid/SettingsTest.kt b/app/src/androidTest/java/com/h/pixeldroid/SettingsTest.kt deleted file mode 100644 index ef864200..00000000 --- a/app/src/androidTest/java/com/h/pixeldroid/SettingsTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.h.pixeldroid - -import android.content.Context -import android.view.Gravity -import androidx.test.core.app.ActivityScenario -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.contrib.DrawerActions -import androidx.test.espresso.contrib.DrawerMatchers.isClosed -import androidx.test.espresso.contrib.NavigationViewActions -import androidx.test.espresso.matcher.ViewMatchers.* -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - - -@RunWith(AndroidJUnit4::class) -class SettingsTest { - @get:Rule - var activityRule: ActivityScenarioRule - = ActivityScenarioRule(MainActivity::class.java) - - @Before - fun before(){ - val preferences = InstrumentationRegistry.getInstrumentation() - .targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE) - preferences.edit().putString("accessToken", "azerty").apply() - preferences.edit().putString("domain", "http://localhost").apply() - } - - @Test - fun testDrawerSettingsButton() { - // Open Drawer to click on navigation. - onView(withId(R.id.drawer_layout)) - .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed. - .perform(DrawerActions.open()); // Open Drawer - - // Start the screen of your activity. - onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_settings)) - - // Check that settings activity was opened. - onView(withText(R.string.signature_title)).check(matches(isDisplayed())) - } - -} \ No newline at end of file diff --git a/app/src/androidTest/java/com/h/pixeldroid/SwipeTest.kt b/app/src/androidTest/java/com/h/pixeldroid/SwipeTest.kt deleted file mode 100644 index cfe6ae40..00000000 --- a/app/src/androidTest/java/com/h/pixeldroid/SwipeTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.h.pixeldroid - -import android.content.Context -import android.content.Intent -import androidx.test.core.app.ActivityScenario -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.action.ViewActions.* -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.matcher.IntentMatchers -import androidx.test.espresso.intent.rule.IntentsTestRule -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import com.google.android.material.tabs.TabLayout -import org.hamcrest.CoreMatchers -import org.hamcrest.Matcher -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class SwipeTest { - @get:Rule - var activityRule: ActivityScenarioRule - = ActivityScenarioRule(MainActivity::class.java) - @Before - fun before(){ - val preferences = getInstrumentation() - .targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE) - preferences.edit().putString("accessToken", "azerty").apply() - preferences.edit().putString("domain", "http://localhost").apply() - } - - @Test - fun swipingLeftStopsAtProfile() { - onView(withId(R.id.main_activity_main_linear_layout)) - .perform(swipeLeft()) // search - .perform(swipeLeft()) // camera - .perform(swipeLeft()) // notifications - .perform(swipeLeft()) // profile - .perform(swipeLeft()) // should stop at profile - onView(withId(R.id.nbFollowersTextView)).check(matches(isDisplayed())) - } - - @Test - fun swipingRightStopsAtHomepage() { - ActivityScenario.launch(MainActivity::class.java).onActivity { - a -> a.findViewById(R.id.tabs).getTabAt(4)?.select() - } // go to the last tab - onView(withId(R.id.main_activity_main_linear_layout)) - .perform(swipeRight()) // notifications - .perform(swipeRight()) // camera - .perform(swipeRight()) // search - .perform(swipeRight()) // homepage - .perform(swipeRight()) // should stop at homepage - onView(withId(R.id.feedList)).check(matches(isDisplayed())) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/LoginActivity.kt b/app/src/main/java/com/h/pixeldroid/LoginActivity.kt index f853efa7..ef99df5e 100644 --- a/app/src/main/java/com/h/pixeldroid/LoginActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/LoginActivity.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.SharedPreferences import android.net.Uri import android.os.Bundle +import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.browser.customtabs.CustomTabsIntent import com.h.pixeldroid.api.PixelfedAPI @@ -42,20 +43,26 @@ class LoginActivity : AppCompatActivity() { val url = intent.data + //Check if the activity was started after the authentication if (url == null || !url.toString().startsWith("$OAUTH_SCHEME://$PACKAGE_ID")) return + loadingAnimation(true) + val code = url.getQueryParameter("code") authenticate(code) } + override fun onStop() { + super.onStop() + loadingAnimation(false) + } + override fun onBackPressed() { } private fun onClickConnect() { - connect_instance_button.isEnabled = false - val normalizedDomain = normalizeDomain(editText.text.toString()) try{ @@ -64,6 +71,8 @@ class LoginActivity : AppCompatActivity() { return failedRegistration(getString(R.string.invalid_domain)) } + loadingAnimation(true) + preferences.edit() .putString("domain", "https://$normalizedDomain") .apply() @@ -125,7 +134,6 @@ class LoginActivity : AppCompatActivity() { return failedRegistration(getString(R.string.browser_launch_failed)) } } - connect_instance_button.isEnabled = true } private fun authenticate(code: String?) { @@ -169,8 +177,19 @@ class LoginActivity : AppCompatActivity() { private fun failedRegistration(message: String = getString(R.string.registration_failed)){ - connect_instance_button.isEnabled = true + loadingAnimation(false) editText.error = message } + private fun loadingAnimation(on: Boolean){ + if(on) { + domainTextInputLayout.visibility = View.GONE + progressLayout.visibility = View.VISIBLE + } + else { + domainTextInputLayout.visibility = View.VISIBLE + progressLayout.visibility = View.GONE + } + } + } diff --git a/app/src/main/java/com/h/pixeldroid/MainActivity.kt b/app/src/main/java/com/h/pixeldroid/MainActivity.kt index 0c623184..a917a5b9 100644 --- a/app/src/main/java/com/h/pixeldroid/MainActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/MainActivity.kt @@ -16,9 +16,9 @@ import com.google.android.material.navigation.NavigationView import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.h.pixeldroid.fragments.CameraFragment -import com.h.pixeldroid.fragments.HomeFragment +import com.h.pixeldroid.fragments.feeds.HomeFragment import com.h.pixeldroid.fragments.MyProfileFragment -import com.h.pixeldroid.fragments.NotificationsFragment +import com.h.pixeldroid.fragments.feeds.NotificationsFragment class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { @@ -44,10 +44,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte val navigationView: NavigationView = findViewById(R.id.nav_view) navigationView.setNavigationItemSelectedListener(this) - val tabs = arrayOf(HomeFragment(), + val tabs = arrayOf( + HomeFragment(), Fragment(), CameraFragment(), - NotificationsFragment(), + NotificationsFragment(), MyProfileFragment()) setupTabs(tabs) diff --git a/app/src/main/java/com/h/pixeldroid/PostActivity.kt b/app/src/main/java/com/h/pixeldroid/PostActivity.kt index 81cef093..74ab8a16 100644 --- a/app/src/main/java/com/h/pixeldroid/PostActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/PostActivity.kt @@ -3,8 +3,8 @@ package com.h.pixeldroid import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.h.pixeldroid.fragments.PostFragment -import com.h.pixeldroid.models.Post -import com.h.pixeldroid.models.Post.Companion.POST_TAG +import com.h.pixeldroid.objects.Status.Companion.POST_TAG +import com.h.pixeldroid.objects.Status class PostActivity : AppCompatActivity() { @@ -14,11 +14,11 @@ class PostActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_post) - val post = intent.getSerializableExtra(POST_TAG) as Post + val status = intent.getSerializableExtra(POST_TAG) as Status postFragment = PostFragment() val arguments = Bundle() - arguments.putSerializable(POST_TAG, post) + arguments.putSerializable(POST_TAG, status) postFragment.arguments = arguments supportFragmentManager.beginTransaction() diff --git a/app/src/main/java/com/h/pixeldroid/fragments/HomeFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/HomeFragment.kt deleted file mode 100644 index 36d1d401..00000000 --- a/app/src/main/java/com/h/pixeldroid/fragments/HomeFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.h.pixeldroid.fragments - -import android.content.Context -import android.content.SharedPreferences -import android.os.Bundle -import android.util.Log - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.h.pixeldroid.BuildConfig -import com.h.pixeldroid.R -import com.h.pixeldroid.api.PixelfedAPI -import com.h.pixeldroid.models.Post -import com.h.pixeldroid.objects.Status -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - - -class HomeFragment : Fragment() { - private lateinit var preferences: SharedPreferences - private lateinit var feed : RecyclerView - private lateinit var adapter : FeedRecyclerViewAdapter - private lateinit var posts : List - - fun setContent(newPosts : ArrayList) { - posts = newPosts - adapter.addPosts(posts) - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(R.layout.fragment_home, container, false) - preferences = this.activity!!.getSharedPreferences( - "${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE - ) - feed = view.findViewById(R.id.feedList) - feed.layoutManager = LinearLayoutManager(context) - adapter = FeedRecyclerViewAdapter(context!!) - feed.adapter = adapter - return view - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}") - val accessToken = preferences.getString("accessToken", "") - var statuses: ArrayList? = null - val newPosts = ArrayList() - - pixelfedAPI.timelineHome("Bearer $accessToken") - .enqueue(object : Callback> { - override fun onResponse(call: Call>, response: Response>) { - if (response.code() == 200) { - statuses = response.body() as ArrayList? - if(!statuses.isNullOrEmpty()) { - for (status in statuses!!) { - newPosts.add(Post(status)) - } - setContent(newPosts) - } - - } - } - - override fun onFailure(call: Call>, t: Throwable) { - Log.e("HomeFragment", t.toString()) - } - }) - } -} diff --git a/app/src/main/java/com/h/pixeldroid/fragments/NotificationsFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/NotificationsFragment.kt deleted file mode 100644 index 7636888b..00000000 --- a/app/src/main/java/com/h/pixeldroid/fragments/NotificationsFragment.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.h.pixeldroid.fragments - -import android.content.Context -import android.content.SharedPreferences -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener -import com.h.pixeldroid.BuildConfig -import com.h.pixeldroid.R -import com.h.pixeldroid.api.PixelfedAPI -import com.h.pixeldroid.objects.Notification -import kotlinx.android.synthetic.main.fragment_notifications_list.* -import kotlinx.android.synthetic.main.fragment_notifications_list.view.* -import kotlinx.android.synthetic.main.fragment_notifications_list.view.swipeRefreshLayout -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -/** - * A fragment representing a list of Items. - */ -class NotificationsFragment : Fragment() { - private lateinit var preferences: SharedPreferences - private lateinit var list : RecyclerView - private lateinit var adapter : NotificationsRecyclerViewAdapter - private lateinit var swipeRefreshLayout: SwipeRefreshLayout - var notifications : List = ArrayList() - - private lateinit var pixelfedAPI: PixelfedAPI - private var accessToken: String? = null - - - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(R.layout.fragment_notifications_list, container, false) - preferences = activity!!.getSharedPreferences( - "${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE - ) - pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}") - accessToken = preferences.getString("accessToken", "") - - swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout) - list = swipeRefreshLayout.list - - // Set the adapter - list.layoutManager = LinearLayoutManager(context) - adapter = NotificationsRecyclerViewAdapter() - list.adapter = adapter - - - - swipeRefreshLayout.setOnRefreshListener { - doRequest() - } - - return view - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - doRequest() - } - fun setContent(notifications : ArrayList) { - this.notifications = notifications - adapter.initializeWith(notifications) - } - - private fun doRequest(){ - val call = pixelfedAPI.notifications("Bearer $accessToken", min_id="1") - call.enqueue(object : Callback> { - override fun onResponse(call: Call>, response: Response>) { - if (response.code() == 200) { - val notifications = response.body()!! as ArrayList - setContent(notifications) - } else{ - Toast.makeText(context,"Something went wrong while refreshing", Toast.LENGTH_SHORT).show() - } - swipeRefreshLayout.isRefreshing = false - } - - override fun onFailure(call: Call>, t: Throwable) { - Toast.makeText(context,"Could not get notifications", Toast.LENGTH_SHORT).show() - Log.e("Notifications", t.toString()) - } - }) - - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/fragments/PostFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/PostFragment.kt index 8fcad445..a1e57077 100644 --- a/app/src/main/java/com/h/pixeldroid/fragments/PostFragment.kt +++ b/app/src/main/java/com/h/pixeldroid/fragments/PostFragment.kt @@ -6,8 +6,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.h.pixeldroid.R -import com.h.pixeldroid.models.Post -import com.h.pixeldroid.models.Post.Companion.POST_TAG +import com.h.pixeldroid.objects.Status.Companion.POST_TAG +import com.h.pixeldroid.objects.Status class PostFragment : Fragment() { @@ -16,9 +16,9 @@ class PostFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - val post = arguments?.getSerializable(POST_TAG) as Post? + val status = arguments?.getSerializable(POST_TAG) as Status? val root = inflater.inflate(R.layout.post_fragment, container, false) - post?.setupPost(this, root) + status?.setupPost(this, root) return root } diff --git a/app/src/main/java/com/h/pixeldroid/fragments/feeds/FeedFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/feeds/FeedFragment.kt new file mode 100644 index 00000000..99a4f85a --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/fragments/feeds/FeedFragment.kt @@ -0,0 +1,98 @@ +package com.h.pixeldroid.fragments.feeds + +import android.content.Context +import android.content.SharedPreferences +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.h.pixeldroid.BuildConfig +import com.h.pixeldroid.R +import com.h.pixeldroid.api.PixelfedAPI +import kotlinx.android.synthetic.main.fragment_feed.* +import kotlinx.android.synthetic.main.fragment_feed.view.* +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +open class FeedFragment: Fragment() { + + var content : List = ArrayList() + + protected var accessToken: String? = null + protected lateinit var pixelfedAPI: PixelfedAPI + protected lateinit var preferences: SharedPreferences + + protected lateinit var list : RecyclerView + protected lateinit var adapter : FeedsRecyclerViewAdapter + private lateinit var swipeRefreshLayout: SwipeRefreshLayout + + protected fun doRequest(call: Call>){ + call.enqueue(object : Callback> { + override fun onResponse(call: Call>, response: Response>) { + if (response.code() == 200) { + val notifications = response.body()!! as ArrayList + setContent(notifications) + } else{ + Toast.makeText(context,"Something went wrong while loading", Toast.LENGTH_SHORT).show() + } + swipeRefreshLayout.isRefreshing = false + progressBar.visibility = View.GONE + } + + override fun onFailure(call: Call>, t: Throwable) { + Toast.makeText(context,"Could not get feed", Toast.LENGTH_SHORT).show() + Log.e("FeedFragment", t.toString()) + } + }) + + } + protected fun setContent(content : ArrayList) { + this.content = content + adapter.initializeWith(content) + } + + + protected fun onCreateView(inflater: LayoutInflater, container: ViewGroup?): View? { + val view = inflater.inflate(R.layout.fragment_feed, container, false) + swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout) + list = swipeRefreshLayout.list + // Set the adapter + list.layoutManager = LinearLayoutManager(context) + + return view + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + preferences = activity!!.getSharedPreferences( + "${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE + ) + + pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}") + accessToken = preferences.getString("accessToken", "") + + } +} + +abstract class FeedsRecyclerViewAdapter: RecyclerView.Adapter() { + + protected val feedContent: ArrayList = arrayListOf() + protected lateinit var context: Context + + override fun getItemCount(): Int = feedContent.size + + open fun initializeWith(content: List){ + feedContent.clear() + feedContent.addAll(content) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/fragments/feeds/HomeFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/feeds/HomeFragment.kt new file mode 100644 index 00000000..eb0c4562 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/fragments/feeds/HomeFragment.kt @@ -0,0 +1,34 @@ +package com.h.pixeldroid.fragments.feeds + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.h.pixeldroid.objects.Status +import kotlinx.android.synthetic.main.fragment_home.* + + +class HomeFragment : FeedFragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = super.onCreateView(inflater, container) + adapter = HomeRecyclerViewAdapter() + list.adapter = adapter + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + + swipeRefreshLayout.setOnRefreshListener { + val call = pixelfedAPI.timelineHome("Bearer $accessToken") + doRequest(call) + } + val call = pixelfedAPI.timelineHome("Bearer $accessToken") + doRequest(call) + } +} diff --git a/app/src/main/java/com/h/pixeldroid/fragments/FeedRecyclerViewAdapter.kt b/app/src/main/java/com/h/pixeldroid/fragments/feeds/HomeRecyclerViewAdapter.kt similarity index 77% rename from app/src/main/java/com/h/pixeldroid/fragments/FeedRecyclerViewAdapter.kt rename to app/src/main/java/com/h/pixeldroid/fragments/feeds/HomeRecyclerViewAdapter.kt index 054066cc..b8aea1da 100644 --- a/app/src/main/java/com/h/pixeldroid/fragments/FeedRecyclerViewAdapter.kt +++ b/app/src/main/java/com/h/pixeldroid/fragments/feeds/HomeRecyclerViewAdapter.kt @@ -1,6 +1,5 @@ -package com.h.pixeldroid.fragments +package com.h.pixeldroid.fragments.feeds -import android.content.Context import android.graphics.Typeface import android.util.DisplayMetrics import androidx.recyclerview.widget.RecyclerView @@ -10,29 +9,20 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.h.pixeldroid.R -import com.h.pixeldroid.models.Post +import com.h.pixeldroid.objects.Status import com.h.pixeldroid.utils.ImageConverter.Companion.setDefaultImage import com.h.pixeldroid.utils.ImageConverter.Companion.setImageViewFromURL import com.h.pixeldroid.utils.ImageConverter.Companion.setRoundImageFromURL -import java.util.ArrayList /** - * [RecyclerView.Adapter] that can display a list of [Post]s + * [RecyclerView.Adapter] that can display a list of Posts */ -class FeedRecyclerViewAdapter( - private val context : Context -) : RecyclerView.Adapter() { - private val posts: ArrayList = ArrayList() - - fun addPosts(newPosts : List) { - val size = posts.size - posts.addAll(newPosts) - notifyItemRangeInserted(size, newPosts.size) - } +class HomeRecyclerViewAdapter() : FeedsRecyclerViewAdapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val inflater = LayoutInflater.from(context) - val view = inflater.inflate(R.layout.post_fragment, parent, false) + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.post_fragment, parent, false) + context = view.context return ViewHolder(view) } @@ -40,7 +30,7 @@ class FeedRecyclerViewAdapter( * Binds the different elements of the Post Model to the view holder */ override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val post = posts[position] + val post = feedContent[position] val metrics = DisplayMetrics() //Limit the height of the different images @@ -72,7 +62,7 @@ class FeedRecyclerViewAdapter( holder.nshares.setTypeface(null, Typeface.BOLD) } - override fun getItemCount(): Int = posts.size + override fun getItemCount(): Int = feedContent.size /** * Represents the posts that will be contained within the feed diff --git a/app/src/main/java/com/h/pixeldroid/fragments/feeds/NotificationsFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/feeds/NotificationsFragment.kt new file mode 100644 index 00000000..d9c260a4 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/fragments/feeds/NotificationsFragment.kt @@ -0,0 +1,44 @@ +package com.h.pixeldroid.fragments.feeds + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.h.pixeldroid.R +import com.h.pixeldroid.objects.Notification +import kotlinx.android.synthetic.main.fragment_feed.* + +/** + * A fragment representing a list of Items. + */ +class NotificationsFragment : FeedFragment() { + + + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + val view = super.onCreateView(inflater, container) + + adapter = NotificationsRecyclerViewAdapter() + list.adapter = adapter + + + return view + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + + swipeRefreshLayout.setOnRefreshListener { + val call = pixelfedAPI.notifications("Bearer $accessToken", min_id="1") + doRequest(call) + } + val call = pixelfedAPI.notifications("Bearer $accessToken", min_id="1") + doRequest(call) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/fragments/NotificationsRecyclerViewAdapter.kt b/app/src/main/java/com/h/pixeldroid/fragments/feeds/NotificationsRecyclerViewAdapter.kt similarity index 85% rename from app/src/main/java/com/h/pixeldroid/fragments/NotificationsRecyclerViewAdapter.kt rename to app/src/main/java/com/h/pixeldroid/fragments/feeds/NotificationsRecyclerViewAdapter.kt index d3cfce9a..70806951 100644 --- a/app/src/main/java/com/h/pixeldroid/fragments/NotificationsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/h/pixeldroid/fragments/feeds/NotificationsRecyclerViewAdapter.kt @@ -1,4 +1,4 @@ -package com.h.pixeldroid.fragments +package com.h.pixeldroid.fragments.feeds import android.content.Context @@ -16,17 +16,14 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.h.pixeldroid.PostActivity import com.h.pixeldroid.R -import com.h.pixeldroid.models.Post -import com.h.pixeldroid.models.Post.Companion.POST_TAG +import com.h.pixeldroid.objects.Status.Companion.POST_TAG import com.h.pixeldroid.objects.Notification import kotlinx.android.synthetic.main.fragment_notifications.view.* /** * [RecyclerView.Adapter] that can display a [Notification] */ -class NotificationsRecyclerViewAdapter: RecyclerView.Adapter() { - private val notifications: ArrayList = arrayListOf() - private lateinit var context: Context +class NotificationsRecyclerViewAdapter: FeedsRecyclerViewAdapter() { private val mOnClickListener: View.OnClickListener @@ -41,7 +38,7 @@ class NotificationsRecyclerViewAdapter: RecyclerView.Adapter { intent = Intent(context, PostActivity::class.java) - intent.putExtra(POST_TAG, Post(notification.status)) + intent.putExtra(POST_TAG, notification.status) } Notification.NotificationType.reblog-> { Toast.makeText(context,"Can't see shares yet, sorry!",Toast.LENGTH_SHORT).show() @@ -55,14 +52,9 @@ class NotificationsRecyclerViewAdapter: RecyclerView.Adapter){ - notifications.clear() - notifications.addAll(newNotifications) - notifyDataSetChanged() - } fun addNotifications(newNotifications: List){ - val oldSize = notifications.size - notifications.addAll(newNotifications) + val oldSize = feedContent.size + feedContent.addAll(newNotifications) notifyItemRangeInserted(oldSize, newNotifications.size) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -74,11 +66,11 @@ class NotificationsRecyclerViewAdapter: RecyclerView.Adapter(R.id.username) - username.text = this.getUsername() - username.setTypeface(null, Typeface.BOLD) - - val usernameDesc = rootView.findViewById(R.id.usernameDesc) - usernameDesc.text = this.getUsernameDescription() - usernameDesc.setTypeface(null, Typeface.BOLD) - - val description = rootView.findViewById(R.id.description) - description.text = this.getDescription() - - val nlikes = rootView.findViewById(R.id.nlikes) - nlikes.text = this.getNLikes() - nlikes.setTypeface(null, Typeface.BOLD) - - val nshares = rootView.findViewById(R.id.nshares) - nshares.text = this.getNShares() - nshares.setTypeface(null, Typeface.BOLD) - - //Setup post and profile images - ImageConverter.setImageViewFromURL( - fragment, - getPostUrl(), - rootView.findViewById(R.id.postPicture) - ) - ImageConverter.setImageViewFromURL( - fragment, - getProfilePicUrl(), - rootView.findViewById(R.id.profilePic) - ) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/objects/Status.kt b/app/src/main/java/com/h/pixeldroid/objects/Status.kt index 838e952e..40a4e39b 100644 --- a/app/src/main/java/com/h/pixeldroid/objects/Status.kt +++ b/app/src/main/java/com/h/pixeldroid/objects/Status.kt @@ -1,5 +1,11 @@ package com.h.pixeldroid.objects +import android.graphics.Typeface +import android.view.View +import android.widget.TextView +import androidx.fragment.app.Fragment +import com.h.pixeldroid.R +import com.h.pixeldroid.utils.ImageConverter import java.io.Serializable /* @@ -43,6 +49,77 @@ data class Status( val pinned: Boolean ) : Serializable { + + companion object { + const val POST_TAG = "postTag" + const val POST_FRAG_TAG = "postFragTag" + } + + fun getPostUrl() : String? = media_attachments?.getOrNull(0)?.url + fun getProfilePicUrl() : String? = account?.avatar + + fun getDescription() : CharSequence { + val description = content as CharSequence + if(description.isEmpty()) { + return "No description" + } + return description + } + + fun getUsername() : CharSequence { + var name = account?.username + if (name.isNullOrEmpty()) { + name = account?.display_name + } + return name!! + } + fun getUsernameDescription() : CharSequence { + return account?.display_name ?: "" + } + + fun getNLikes() : CharSequence { + val nLikes : Int = favourites_count ?: 0 + return "$nLikes Likes" + } + + fun getNShares() : CharSequence { + val nShares : Int = reblogs_count ?: 0 + return "$nShares Shares" + } + + fun setupPost(fragment: Fragment, rootView : View) { + //Setup username as a button that opens the profile + val username = rootView.findViewById(R.id.username) + username.text = this.getUsername() + username.setTypeface(null, Typeface.BOLD) + + val usernameDesc = rootView.findViewById(R.id.usernameDesc) + usernameDesc.text = this.getUsernameDescription() + usernameDesc.setTypeface(null, Typeface.BOLD) + + val description = rootView.findViewById(R.id.description) + description.text = this.getDescription() + + val nlikes = rootView.findViewById(R.id.nlikes) + nlikes.text = this.getNLikes() + nlikes.setTypeface(null, Typeface.BOLD) + + val nshares = rootView.findViewById(R.id.nshares) + nshares.text = this.getNShares() + nshares.setTypeface(null, Typeface.BOLD) + + //Setup post and profile images + ImageConverter.setImageViewFromURL( + fragment, + getPostUrl(), + rootView.findViewById(R.id.postPicture) + ) + ImageConverter.setImageViewFromURL( + fragment, + getProfilePicUrl(), + rootView.findViewById(R.id.profilePic) + ) + } enum class Visibility : Serializable { public, unlisted, private, direct } diff --git a/app/src/main/java/com/h/pixeldroid/utils/ImageConverter.kt b/app/src/main/java/com/h/pixeldroid/utils/ImageConverter.kt index 06b6986f..bd821389 100644 --- a/app/src/main/java/com/h/pixeldroid/utils/ImageConverter.kt +++ b/app/src/main/java/com/h/pixeldroid/utils/ImageConverter.kt @@ -8,7 +8,6 @@ import androidx.fragment.app.FragmentActivity import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.h.pixeldroid.R -import com.h.pixeldroid.models.Post class ImageConverter { companion object { diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index a7140b7f..0891632e 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -6,34 +6,58 @@ android:layout_height="match_parent" tools:context=".LoginActivity"> -