Merge branch 'master' into swap_username

This commit is contained in:
mjaillot 2021-08-13 15:07:36 +02:00
commit 9ef319bb7e
192 changed files with 3052 additions and 1469 deletions

View File

@ -1,34 +0,0 @@
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

29
.fdroid.yml.template Normal file
View File

@ -0,0 +1,29 @@
Categories:
- Internet
License: GPL-3.0-or-later
AuthorName: PixelDroid team
AuthorEmail: contact@pixeldroid.org
WebSite: https://pixeldroid.org/
SourceCode: https://gitlab.shinice.net/pixeldroid/PixelDroid
IssueTracker: https://gitlab.shinice.net/pixeldroid/PixelDroid/issues
Translation: https://weblate.pixeldroid.org/projects/pixeldroid/
Changelog: https://gitlab.shinice.net/pixeldroid/PixelDroid/-/releases
Liberapay: PixelDroid
AutoName: PixelDroid
RepoType: git
Repo: https://gitlab.shinice.net/pixeldroid/PixelDroid.git
Builds:
- versionName: ${versionName}
versionCode: ${versionCode}
commit: HEAD
subdir: app
gradle:
- yes
AutoUpdateMode: Version %v
UpdateCheckMode: Tags
CurrentVersion: 1.0.beta1
CurrentVersionCode: 1

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
app/src/main/assets/licenses.html linguist-generated

1
.gitignore vendored
View File

@ -14,5 +14,6 @@
.cxx
.idea
app/release
app/debug
app/lint
lint

View File

@ -7,6 +7,14 @@ variables:
TARGET: "default"
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
# Basic android and gradle stuff
# Check linting
lintDebug:
@ -52,6 +60,62 @@ emulatorTest:
- cat app/build/reports/jacoco/jacocoTestReport/html/index.html | grep -o 'Total[^%]*%'
artifacts:
when: always
paths:
- ./app/build/reports/jacoco/jacocoTestReport/
expire_in: 1 week
fdroid build:
image: registry.gitlab.com/fdroid/ci-images-client:latest
allow_failure: true
artifacts:
paths:
- unsigned/
when: always
only:
- tags
cache:
key: "$CI_JOB_NAME"
paths:
- .gradle
script:
# Put the correct versionName and versionCode in the .fdroid.yml
- sed -e "s/\${versionName}/$(grep "versionName " app/build.gradle | awk '{print $2}')/" -e "s/\${versionCode}/$(grep "versionCode" app/build.gradle | awk '{print $2}')/" .fdroid.yml.template > .fdroid.yml
- rm .fdroid.yml.template
- test -d build || mkdir build
- test -d fdroidserver || mkdir fdroidserver
- git ls-remote https://gitlab.com/fdroid/fdroidserver.git master
- curl --silent https://gitlab.com/fdroid/fdroidserver/-/archive/master/fdroidserver-master.tar.gz
| tar -xz --directory=fdroidserver --strip-components=1
- export PATH="`pwd`/fdroidserver:$PATH"
- export PYTHONPATH="$CI_PROJECT_DIR/fdroidserver:$CI_PROJECT_DIR/fdroidserver/examples"
- export PYTHONUNBUFFERED=true
- bash fdroidserver/buildserver/setup-env-vars $ANDROID_HOME
- adduser --disabled-password --gecos "" vagrant
- ln -s $CI_PROJECT_DIR/fdroidserver /home/vagrant/fdroidserver
- mkdir -p /vagrant/cache
- wget -q https://services.gradle.org/distributions/gradle-5.6.2-bin.zip
--output-document=/vagrant/cache/gradle-5.6.2-bin.zip
- bash fdroidserver/buildserver/provision-gradle
- bash fdroidserver/buildserver/provision-apt-get-install http://deb.debian.org/debian
- source /etc/profile.d/bsenv.sh
- apt-get dist-upgrade
# install fdroidserver from git, with deps from Debian, until fdroidserver
# is stable enough to include all the things needed here
- apt-get install -t stretch-backports
fdroidserver
python3-asn1crypto
python3-ruamel.yaml
yamllint
- apt-get purge fdroidserver
- export GRADLE_USER_HOME=$PWD/.gradle
# each `fdroid build --on-server` run expects sudo, then uninstalls it
- set -x
- apt-get install sudo
- fdroid fetchsrclibs --verbose
# this builds the latest version of the app from its source dir, using the build recipe in .fdroid.yml
- fdroid build --verbose --on-server --no-tarball

View File

@ -1,10 +1,13 @@
# PixelDroid
![Pixeldroid project logo](pixeldroid_logo.png)
Free (as in freedom) Android client for Pixelfed, the federated image sharing platform.
Free (as in freedom) Android client for Pixelfed, the federated image sharing platform. Licensed under the GNU General Public License version 3 (or any later version)
[![Build Status](https://gitlab.shinice.net/pixeldroid/PixelDroid/badges/master/pipeline.svg)](https://gitlab.shinice.net/pixeldroid/PixelDroid/pipelines) [![Maintainability](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/maintainability)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/maintainability) [![Translation status](https://weblate.pixeldroid.org/widgets/pixeldroid/-/pixeldroid/svg-badge.svg)](https://weblate.pixeldroid.org/engage/pixeldroid/?utm_source=widget)
<a href=https://apt.izzysoft.de/fdroid/index/apk/com.h.pixeldroid><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="170"></a>
<a href="https://f-droid.org/en/packages/org.pixeldroid.app/">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" width="206">
</a>
## 🔧 Compiling the code yourself
If you want to try out PixelDroid on your own device, you can compile the source code yourself. To do that you can install [Android Studio](https://developer.android.com/studio/).

Binary file not shown.

View File

@ -7,6 +7,9 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco'
// Force latest version of Jacoco, initially done to resolve https://github.com/jacoco/jacoco/issues/1155
jacoco.toolVersion = "0.8.7"
android {
@ -21,11 +24,11 @@ android {
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
}
defaultConfig {
applicationId "com.h.pixeldroid"
applicationId "org.pixeldroid.app"
minSdkVersion 23
targetSdkVersion 30
versionCode 10
versionName "1.0.alpha9"
versionCode 3
versionName "1.0.beta3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
@ -36,15 +39,15 @@ android {
sourceSets {
main.java.srcDirs += 'src/main/java'
test.java.srcDirs += 'src/test/java'
staging.res.srcDirs += 'src/debug/res'
androidTest.java.srcDirs += 'src/androidTest/java'
}
testBuildType "staging"
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix "-debug"
}
staging {
initWith debug
@ -61,12 +64,12 @@ android {
localProperties.load(new FileInputStream(rootProject.file("local.properties")))
}
buildConfigField "String", "USER_ID", System.getenv("USER_ID") ?: localProperties['USER_ID']
buildConfigField "String", "INSTANCE_URI", System.getenv("INSTANCE_URI") ?: localProperties['INSTANCE_URI']
buildConfigField "String", "ACCESS_TOKEN", System.getenv("ACCESS_TOKEN") ?: localProperties['ACCESS_TOKEN']
buildConfigField "String", "REFRESH_TOKEN", System.getenv("REFRESH_TOKEN") ?: localProperties['REFRESH_TOKEN']
buildConfigField "String", "CLIENT_ID", System.getenv("CLIENT_ID") ?: localProperties['CLIENT_ID']
buildConfigField "String", "CLIENT_SECRET", System.getenv("CLIENT_SECRET") ?: localProperties['CLIENT_SECRET']
buildConfigField "String", "USER_ID", System.getenv("USER_ID") ?: localProperties['USER_ID'] ?: ""
buildConfigField "String", "INSTANCE_URI", System.getenv("INSTANCE_URI") ?: localProperties['INSTANCE_URI'] ?: ""
buildConfigField "String", "ACCESS_TOKEN", System.getenv("ACCESS_TOKEN") ?: localProperties['ACCESS_TOKEN'] ?: ""
buildConfigField "String", "REFRESH_TOKEN", System.getenv("REFRESH_TOKEN") ?: localProperties['REFRESH_TOKEN'] ?: ""
buildConfigField "String", "CLIENT_ID", System.getenv("CLIENT_ID") ?: localProperties['CLIENT_ID'] ?: ""
buildConfigField "String", "CLIENT_SECRET", System.getenv("CLIENT_SECRET") ?: localProperties['CLIENT_SECRET'] ?: ""
}
release {
minifyEnabled true
@ -74,6 +77,14 @@ android {
proguardFiles 'proguard-rules.pro'
}
}
/**
* Make a string with the application_id (available in xml etc)
*/
android.applicationVariants.all { variant ->
variant.resValue 'string', 'application_id', variant.applicationId
}
testOptions {
animationsDisabled true
@ -92,37 +103,38 @@ dependencies {
/**
* AndroidX dependencies:
*/
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation "androidx.browser:browser:1.3.0"
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4'
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-beta02'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.0"
implementation "androidx.annotation:annotation:1.1.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.paging:paging-runtime-ktx:3.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
implementation "androidx.annotation:annotation:1.2.0"
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation "androidx.activity:activity-ktx:1.2.1"
implementation "androidx.activity:activity-ktx:1.2.3"
implementation 'androidx.fragment:fragment-ktx:1.3.5'
// Use the most recent version of CameraX
def cameraX_version = '1.0.0-rc03'
def cameraX_version = '1.0.0'
implementation "androidx.camera:camera-core:${cameraX_version}"
implementation "androidx.camera:camera-camera2:${cameraX_version}"
// CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:$cameraX_version"
// CameraX View class
implementation 'androidx.camera:camera-view:1.0.0-alpha22'
implementation 'androidx.camera:camera-view:1.0.0-alpha25'
def room_version = "2.3.0-beta03"
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
@ -136,17 +148,17 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0'
//Dagger (dependency injection)
implementation 'com.google.dagger:dagger-android:2.30.1'
implementation 'com.google.dagger:dagger-android-support:2.30.1'
implementation 'com.google.dagger:dagger-android:2.37'
implementation 'com.google.dagger:dagger-android-support:2.37'
// if you use the support libraries
kapt 'com.google.dagger:dagger-android-processor:2.30.1'
kapt 'com.google.dagger:dagger-compiler:2.30.1'
kapt 'com.google.dagger:dagger-android-processor:2.37'
kapt 'com.google.dagger:dagger-compiler:2.37'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
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.20'
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.github.connyduck:sparkbutton:4.1.0'
@ -154,26 +166,26 @@ dependencies {
implementation 'info.androidhive:imagefilters:1.0.7'
implementation 'com.github.yalantis:ucrop:2.2.6-native'
implementation("com.github.bumptech.glide:glide:4.11.0") {
implementation('com.github.bumptech.glide:glide:4.12.0') {
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") {
implementation 'com.github.bumptech.glide:okhttp-integration:4.12.0'
implementation('com.github.bumptech.glide:recyclerview-integration:4.12.0') {
// Excludes the support library because it's already included by Glide.
transitive = false
}
kapt 'com.github.bumptech.glide:compiler:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "com.mikepenz:materialdrawer:8.1.8"
implementation 'com.mikepenz:materialdrawer:8.4.1'
// Add for NavController support
implementation "com.mikepenz:materialdrawer-nav:8.1.5"
implementation 'com.mikepenz:materialdrawer-nav:8.4.0'
//iconics
implementation "com.mikepenz:iconics-core:5.0.3"
implementation "com.mikepenz:materialdrawer-iconics:8.1.8"
implementation "com.mikepenz:iconics-core:5.2.8"
implementation 'com.mikepenz:materialdrawer-iconics:8.4.1'
implementation "com.mikepenz:iconics-views:5.0.3"
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'
@ -190,13 +202,15 @@ dependencies {
// debugImplementation required vs testImplementation: https://issuetracker.google.com/issues/128612536
//noinspection FragmentGradleConfiguration
stagingImplementation("androidx.fragment:fragment-testing:1.3.1") {
stagingImplementation("androidx.fragment:fragment-testing:1.3.5") {
exclude group:'androidx.test', module:'monitor'
}
//stagingImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
testImplementation 'com.github.tomakehurst:wiremock-jre8:2.27.2'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
testImplementation 'junit:junit:4.13.1'
testImplementation 'junit:junit:4.13.2'
testImplementation "androidx.room:room-testing:$room_version"
@ -227,10 +241,10 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['connectedStagingAndroidTe
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def kotlinDebugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
def kotlinTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/staging", excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
getSourceDirectories().from(files([mainSrc]))
getClassDirectories().from(files([kotlinDebugTree]))
getClassDirectories().from(files([kotlinTree]))
getExecutionData().from(fileTree(dir: project.buildDir, includes: [
'outputs/code_coverage/stagingAndroidTest/connected/*coverage.ec',
@ -238,4 +252,4 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['connectedStagingAndroidTe
'jacoco/testStagingUnitTest.exec'
]))
}
}

View File

@ -16,12 +16,12 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/mikepenz/MaterialDrawer
- artifact: org.jetbrains.kotlin:kotlin-android-extensions-runtime:+
name: kotlin-android-extensions-runtime
copyrightHolder: JetBrains s.r.o. and contributors
license: The Apache License, Version 2.0
- artifact: androidx.startup:startup-runtime:+
name: startup-runtime
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
url: https://developer.android.com/jetpack/androidx/releases/startup#1.0.0
- artifact: com.mikepenz:iconics-views:+
name: iconics-views
copyrightHolder: Mike Penz and contributors
@ -793,4 +793,4 @@
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/tracing#1.0.0
url: https://developer.android.com/jetpack/androidx/releases/tracing#1.0.0

View File

@ -37,9 +37,9 @@
# APP SPECIFIC OPTIONS
# keep members of our model classes, they are used in json de/serialization
-keepclassmembers class com.h.pixeldroid.utils.api.objects.* { *; }
-keepclassmembers class org.pixeldroid.app.utils.api.objects.* { *; }
-keep public enum com.h.pixeldroid.utils.api.objects.*$** {
-keep public enum org.pixeldroid.app.utils.api.objects.*$** {
**[] $VALUES;
public *;
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.Manifest
import android.content.Context
@ -10,11 +10,11 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.rule.GrantPermissionRule
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.postCreation.camera.CameraFragment
import com.h.pixeldroid.testUtility.clearData
import com.h.pixeldroid.testUtility.initDB
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.postCreation.camera.CameraFragment
import org.pixeldroid.app.testUtility.clearData
import org.pixeldroid.app.testUtility.initDB
import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher
import org.junit.After

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import androidx.test.core.app.ActivityScenario
@ -12,8 +12,9 @@ import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
import com.h.pixeldroid.testUtility.*
import com.h.pixeldroid.utils.db.AppDatabase
import org.hamcrest.Matchers.allOf
import org.pixeldroid.app.testUtility.*
import org.pixeldroid.app.utils.db.AppDatabase
import org.junit.After
import org.junit.Before
import org.junit.Rule
@ -107,8 +108,10 @@ class DrawerMenuTest {
// Start the screen of your activity.
onView(withText(R.string.menu_account)).perform(click())
// Check that profile activity was opened.
waitForView(R.id.editButton)
onView(withId(R.id.editButton)).check(matches(isDisplayed()))
val followersText = context.resources.getQuantityString(R.plurals.nb_followers, 2, 2)
waitForView(R.id.nbFollowingTextView, allOf(withId(R.id.nbFollowersTextView), withText(followersText)))
onView(withText(followersText)).perform(click())
waitForView(R.id.account_entry_avatar)
@ -120,8 +123,10 @@ class DrawerMenuTest {
// Start the screen of your activity.
onView(withText(R.string.menu_account)).perform(click())
// Check that profile activity was opened.
waitForView(R.id.editButton)
onView(withId(R.id.editButton)).check(matches(isDisplayed()))
val followingText = context.resources.getQuantityString(R.plurals.nb_following, 3, 3)
waitForView(R.id.nbFollowingTextView, allOf(withId(R.id.nbFollowingTextView), withText(followingText)))
onView(withText(followingText)).perform(click())
waitForView(R.id.account_entry_avatar)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import android.content.Intent
@ -22,13 +22,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
import com.h.pixeldroid.postCreation.photoEdit.ThumbnailAdapter
import com.h.pixeldroid.settings.AboutActivity
import com.h.pixeldroid.testUtility.clearData
import com.h.pixeldroid.testUtility.clickChildViewWithId
import com.h.pixeldroid.testUtility.slowSwipeLeft
import com.h.pixeldroid.testUtility.waitForView
import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
import org.pixeldroid.app.postCreation.photoEdit.ThumbnailAdapter
import org.pixeldroid.app.settings.AboutActivity
import org.pixeldroid.app.testUtility.clearData
import org.pixeldroid.app.testUtility.clickChildViewWithId
import org.pixeldroid.app.testUtility.slowSwipeLeft
import org.pixeldroid.app.testUtility.waitForView
import org.hamcrest.CoreMatchers.allOf
import org.junit.*
import org.junit.Assert.assertTrue
@ -70,7 +70,9 @@ class EditPhotoTest {
file.writeBitmap(image)
uri = file.toUri()
}
val intent = Intent(context, PhotoEditActivity::class.java).putExtra("picture_uri", uri)
val intent = Intent(context, PhotoEditActivity::class.java)
.putExtra(PhotoEditActivity.PICTURE_URI, uri)
.putExtra(PhotoEditActivity.PICTURE_POSITION, 0)
activityScenario = ActivityScenario.launch<PhotoEditActivity>(intent).onActivity{a -> activity = a}

View File

@ -1,10 +1,12 @@
package com.h.pixeldroid
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.action.ViewActions
import androidx.test.espresso.action.ViewActions.openLinkWithText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
@ -12,17 +14,21 @@ import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.posts.StatusViewHolder
import com.h.pixeldroid.testUtility.*
import org.hamcrest.CoreMatchers.not
import org.hamcrest.CoreMatchers.*
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.posts.StatusViewHolder
import org.pixeldroid.app.testUtility.*
import org.hamcrest.core.IsInstanceOf
import org.hamcrest.core.StringContains.containsString
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.rules.Timeout
import org.junit.runner.Description
import org.junit.runner.RunWith
import org.junit.runners.model.Statement
@RunWith(AndroidJUnit4::class)
class HomeFeedTest {
@ -31,8 +37,9 @@ class HomeFeedTest {
private lateinit var db: AppDatabase
private lateinit var context: Context
@get:Rule
var globalTimeout: Timeout = Timeout.seconds(100)
@Rule @JvmField
var repeatRule: RepeatRule = RepeatRule()
@Before
fun before(){
@ -47,6 +54,10 @@ class HomeFeedTest {
)
db.close()
activityScenario = ActivityScenario.launch(MainActivity::class.java)
waitForView(R.id.username)
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(0))
}
@After
fun after() {
@ -54,6 +65,7 @@ class HomeFeedTest {
}
@Test
@RepeatTest
fun clickingTabOnAlbumShowsNextPhoto() {
//Wait for the feed to load
waitForView(R.id.postPager)
@ -66,6 +78,40 @@ class HomeFeedTest {
}
onView(first(withId(R.id.postPager))).check(matches(isDisplayed()))
}
@Test
@RepeatTest
fun tabReClickScrollUp() {
//Wait for the feed to load
waitForView(R.id.postPager)
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(4))
onView(first(IsInstanceOf.instanceOf(TabLayout.TabView::class.java))).perform(ViewActions.click())
onView(first(withId(R.id.description))).check(matches(withText(containsString("@user2"))));
}
@Test
@RepeatTest
fun hashtag() {
//Wait for the feed to load
waitForView(R.id.postPager)
onView(allOf(withClassName(endsWith("RecyclerView")), not(withId(R.id.material_drawer_recycler_view))))
.perform(
scrollToPosition<StatusViewHolder>(3)
)
onView(allOf(withText(containsString("randomNoise"))))
.perform(clickClickableSpan("#randomNoise"))
waitForView(R.id.action_bar, allOf(withText("#randomNoise"), not(withId(R.id.description))))
onView(withId(R.id.action_bar)).check(matches(isDisplayed()));
onView(allOf(withText("#randomNoise"), not(withId(R.id.description)))).check(matches(withParent(withId(R.id.action_bar))));
}
/*
@Test
fun clickingReblogButtonWorks() {
@ -127,6 +173,7 @@ class HomeFeedTest {
}*/
@Test
@RepeatTest
fun clickingUsernameOpensProfile() {
waitForView(R.id.username)
@ -137,6 +184,7 @@ class HomeFeedTest {
}
@Test
@RepeatTest
fun clickingProfilePicOpensProfile() {
waitForView(R.id.profilePic)
@ -147,6 +195,7 @@ class HomeFeedTest {
}
@Test
@RepeatTest
fun clickingMentionOpensProfile() {
waitForView(R.id.description)
@ -156,13 +205,6 @@ class HomeFeedTest {
onView(first(withId(R.id.username))).check(matches(isDisplayed()))
}
/*
@Test
fun clickingHashTagsWorks() {
onView(withId(R.id.list)).perform(
actionOnItemAtPosition<StatusViewHolder>(1, clickChildViewWithId(R.id.description))
)
onView(withId(R.id.list)).check(matches(isDisplayed()))
}
@Test
@ -217,6 +259,7 @@ class HomeFeedTest {
.check(matches(hasDescendant(withId(R.id.comment))))
}*/
@RepeatTest
@Test
fun performClickOnSensitiveWarning() {
waitForView(R.id.username)
@ -232,11 +275,10 @@ class HomeFeedTest {
}
@Test
@RepeatTest
fun performClickOnSensitiveWarningTabs() {
waitForView(R.id.username)
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(0))
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withId(R.id.list))

View File

@ -1,17 +1,10 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import android.content.Intent
import android.text.SpannableString
import android.text.style.ClickableSpan
import android.view.View
import android.widget.TextView
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.contrib.DrawerActions
@ -23,17 +16,14 @@ import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.posts.StatusViewHolder
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_TAG
import com.h.pixeldroid.settings.AboutActivity
import com.h.pixeldroid.testUtility.*
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.posts.StatusViewHolder
import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_TAG
import org.pixeldroid.app.settings.AboutActivity
import org.pixeldroid.app.testUtility.*
import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.junit.After
import org.junit.Before
import org.junit.Rule

View File

@ -1,5 +1,5 @@
package com.h.pixeldroid
/*
package org.pixeldroid.app
import android.content.Context
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
@ -9,33 +9,20 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import com.h.pixeldroid.testUtility.clearData
import com.h.pixeldroid.testUtility.initDB
import com.h.pixeldroid.utils.db.AppDatabase
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.*
import org.pixeldroid.app.testUtility.clearData
import org.pixeldroid.app.testUtility.initDB
import org.pixeldroid.app.utils.db.AppDatabase
import org.junit.rules.Timeout
import org.junit.runner.RunWith
import org.pixeldroid.app.testUtility.waitForView
@RunWith(AndroidJUnit4::class)
@Ignore("Ignore until we can get TestButler to work on CI")
class LoginActivityOfflineTest {
private lateinit var context: Context
companion object {
fun switchAirplaneMode() {
val device = UiDevice.getInstance(getInstrumentation())
device.openQuickSettings()
device.findObject(UiSelector().textContains("Airplane")).click()
device.pressHome()
}
}
private lateinit var db: AppDatabase
@get:Rule
@ -43,7 +30,8 @@ class LoginActivityOfflineTest {
@Before
fun before() {
switchAirplaneMode()
//TestButler.setWifiState(false)
//TestButler.setGsmState(false)
context = ApplicationProvider.getApplicationContext<Context>()
db = initDB(context)
db.clearAllTables()
@ -52,19 +40,22 @@ class LoginActivityOfflineTest {
@Test
fun emptyDBandOfflineModeDisplayCorrectMessage() {
waitForView(R.id.login_activity_connection_required)
onView(withId(R.id.login_activity_connection_required)).check(matches(isDisplayed()))
}
@Test
fun retryButtonReloadsLoginActivity() {
waitForView(R.id.login_activity_connection_required_button)
onView(withId(R.id.login_activity_connection_required_button)).perform(click())
onView(withId(R.id.login_activity_connection_required)).check(matches(isDisplayed()))
}
@After
fun after() {
switchAirplaneMode()
//TestButler.setWifiState(true)
//TestButler.setGsmState(true)
db.close()
clearData()
}
}*/
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import android.content.Intent
@ -13,22 +13,18 @@ import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasErrorText
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.testUtility.clearData
import com.h.pixeldroid.testUtility.initDB
import com.h.pixeldroid.testUtility.testiTesto
import com.h.pixeldroid.testUtility.testiTestoInstance
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.testUtility.clearData
import org.pixeldroid.app.testUtility.initDB
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.Timeout
import org.junit.runner.RunWith
import org.pixeldroid.app.testUtility.PACKAGE_ID
@RunWith(AndroidJUnit4::class)
class LoginActivityOnlineTest {
@ -44,7 +40,7 @@ class LoginActivityOnlineTest {
fun setup() {
context = ApplicationProvider.getApplicationContext()
context = ApplicationProvider.getApplicationContext()
pref = context.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
pref = context.getSharedPreferences("${PACKAGE_ID}.pref", Context.MODE_PRIVATE)
pref.edit().clear().apply()
db = initDB(context)
db.clearAllTables()
@ -81,7 +77,7 @@ class LoginActivityOnlineTest {
.putString("clientID", "iwndoiuqwnd")
.putString("clientSecret", "wlifowed")
.apply()
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=sdfdqsf")
val uri = Uri.parse("oauth2redirect://${PACKAGE_ID}?code=sdfdqsf")
val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
ActivityScenario.launch<LoginActivity>(intent)
onView(withId(R.id.editText)).check(matches(
@ -91,7 +87,7 @@ class LoginActivityOnlineTest {
@Test
fun incompleteIntentReturnInfoFailsTest() {
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=")
val uri = Uri.parse("oauth2redirect://${PACKAGE_ID}?code=")
val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
ActivityScenario.launch<LoginActivity>(intent)
onView(withId(R.id.editText)).check(matches(
@ -117,7 +113,7 @@ class LoginActivityOnlineTest {
.putString("clientID", testiTesto.clientId)
.putString("clientSecret", testiTesto.clientSecret)
.apply()
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=$testiTesto.")
val uri = Uri.parse("oauth2redirect://org.pixeldroid.app?code=$testiTesto.")
val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
ActivityScenario.launch<LoginActivity>(intent)
Thread.sleep(1000)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Intent
import android.content.Intent.ACTION_VIEW
@ -15,12 +15,9 @@ import androidx.test.espresso.intent.matcher.IntentMatchers.hasDataString
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.UiDevice
import com.h.pixeldroid.BuildConfig.INSTANCE_URI
import com.h.pixeldroid.testUtility.clearData
import com.h.pixeldroid.testUtility.waitForView
import org.pixeldroid.app.BuildConfig.INSTANCE_URI
import org.pixeldroid.app.testUtility.clearData
import org.pixeldroid.app.testUtility.waitForView
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.Matcher

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
@ -11,8 +11,8 @@ 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 com.h.pixeldroid.testUtility.*
import com.h.pixeldroid.utils.db.AppDatabase
import org.pixeldroid.app.testUtility.*
import org.pixeldroid.app.utils.db.AppDatabase
import org.junit.After
import org.junit.Before
import org.junit.Test

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.Manifest
import android.content.ClipData
@ -7,26 +7,21 @@ import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.util.Log
import android.view.View.VISIBLE
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.h.pixeldroid.postCreation.PostCreationActivity
import com.h.pixeldroid.postCreation.photoEdit.ThumbnailAdapter
import com.h.pixeldroid.settings.AboutActivity
import com.h.pixeldroid.testUtility.*
import com.h.pixeldroid.utils.db.AppDatabase
import org.hamcrest.CoreMatchers.not
import org.pixeldroid.app.postCreation.PostCreationActivity
import org.pixeldroid.app.settings.AboutActivity
import org.pixeldroid.app.testUtility.*
import org.pixeldroid.app.utils.db.AppDatabase
import org.junit.*
import org.junit.rules.Timeout
import org.junit.runner.RunWith
@ -44,7 +39,8 @@ class PostCreationActivityTest {
val globalTimeout: Timeout = Timeout.seconds(30)
@get:Rule
val mRuntimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
val mRuntimePermissionRule: GrantPermissionRule =
GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
private fun File.writeBitmap(bitmap: Bitmap) {
outputStream().use { out ->
@ -112,32 +108,101 @@ class PostCreationActivityTest {
// should send on main activity
onView(withId(R.id.retry_upload_button)).check(matches(not(isDisplayed())))
}
*/
/**
* Makes sure the [org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity] is launched
* when the edit button is pressed
*/
@Test
fun editImage() {
Thread.sleep(1000)
waitForView(R.id.postTextInputLayout)
onView(withId(R.id.image_grid)).perform(
RecyclerViewActions.actionOnItemAtPosition<PostCreationActivity.PostCreationAdapter.ViewHolder>(
0,
CustomMatchers.clickChildViewWithId(R.id.galleryImage)
)
)
Thread.sleep(1000)
onView(withId(R.id.editPhotoButton)).perform(click())
onView(withId(R.id.recycler_view))
.perform(
RecyclerViewActions.actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(
2,
CustomMatchers.clickChildViewWithId(R.id.thumbnail)
)
)
Thread.sleep(1000)
onView(withId(R.id.action_save)).perform(click())
Thread.sleep(1000)
onView(withId(R.id.carousel)).check(matches(isDisplayed()))
waitForView(R.id.cropImageButton)
}
/**
* Switch from carousel to grid and back
*/
@Test
fun carouselSwitch() {
waitForView(R.id.postTextInputLayout)
onView(withId(R.id.switchToGridButton)).perform(click())
waitForView(R.id.galleryImage)
onView(withId(R.id.switchToCarouselButton)).perform(click())
waitForView(R.id.btn_previous)
}
/**
* Delete images and check if it worked
*/
@Test
fun deleteImages() {
waitForView(R.id.postTextInputLayout)
onView(withId(R.id.removePhotoButton)).perform(click()).perform(click())
onView(withId(R.id.switchToGridButton)).perform(click())
onView(withId(R.id.galleryImage)).check(doesNotExist())
onView(withId(R.id.addPhotoSquare)).check(matches(isDisplayed()))
}
/**
* Type media description and check it's saved
*/
@Test
fun mediaDescription() {
waitForView(R.id.postTextInputLayout)
fun typeDescription(text: String) {
onView(withId(R.id.tv_caption)).perform(click())
waitForView(R.id.editTextMediaDescription)
onView(withId(R.id.editTextMediaDescription)).perform(typeText(text))
onView(withId(R.id.imageDescriptionButton)).perform(click())
onView(withId(R.id.tv_caption)).check(matches(withText(text)))
}
val typedText1 = "Testi testo description"
typeDescription(typedText1)
onView(withId(R.id.btn_next)).perform(click())
val typedText2 = "Description 2"
typeDescription(typedText2)
onView(withId(R.id.btn_previous)).perform(click())
onView(withId(R.id.tv_caption)).check(matches(withText(typedText1)))
onView(withId(R.id.btn_next)).perform(click())
onView(withId(R.id.tv_caption)).check(matches(withText(typedText2)))
}
/**
* Makes sure the [org.pixeldroid.app.postCreation.camera.CameraActivity] is launched
* when the add image button is pressed
*/
@Test
fun addImage() {
waitForView(R.id.postTextInputLayout)
onView(withId(R.id.addPhotoButton)).perform(click())
waitForView(R.id.camera_activity_fragment)
}
/*
@Test
fun cancelEdit() {
onView(withId(R.id.image_grid)).perform(

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import android.content.Intent
@ -17,8 +17,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.testUtility.*
import com.h.pixeldroid.utils.db.AppDatabase
import org.pixeldroid.app.testUtility.*
import org.pixeldroid.app.utils.db.AppDatabase
import org.hamcrest.Matcher
import org.junit.After
import org.junit.Before

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import android.content.Intent
@ -13,16 +13,14 @@ import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.h.pixeldroid.BuildConfig.INSTANCE_URI
import com.h.pixeldroid.posts.PostActivity
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.utils.api.objects.*
import com.h.pixeldroid.testUtility.clearData
import com.h.pixeldroid.testUtility.initDB
import com.h.pixeldroid.testUtility.testiTesto
import com.h.pixeldroid.testUtility.testiTestoInstance
import org.pixeldroid.app.BuildConfig.INSTANCE_URI
import org.pixeldroid.app.posts.PostActivity
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.api.objects.*
import org.pixeldroid.app.testUtility.clearData
import org.pixeldroid.app.testUtility.initDB
import org.pixeldroid.app.testUtility.testiTesto
import org.pixeldroid.app.testUtility.testiTestoInstance
import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.Matcher
import org.junit.*

View File

@ -1,29 +1,24 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import android.content.Intent
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.h.pixeldroid.postCreation.PostCreationActivity
import com.h.pixeldroid.profile.ProfileActivity
import com.h.pixeldroid.testUtility.*
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.db.AppDatabase
import okhttp3.internal.wait
import org.pixeldroid.app.profile.ProfileActivity
import org.pixeldroid.app.testUtility.*
import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.db.AppDatabase
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.io.Serializable
@RunWith(AndroidJUnit4::class)
class ProfileTest {

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.testUtility
package org.pixeldroid.app.testUtility
import android.text.SpannableString
import android.text.style.ClickableSpan
@ -8,6 +8,7 @@ import android.widget.TextView
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.*
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.BoundedMatcher
@ -15,15 +16,43 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.util.HumanReadables
import androidx.test.espresso.util.TreeIterables
import com.h.pixeldroid.R
import org.hamcrest.BaseMatcher
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.junit.rules.TestRule
import org.junit.runners.model.Statement
import org.pixeldroid.app.R
import java.util.concurrent.TimeoutException
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.ANNOTATION_CLASS)
annotation class RepeatTest(val value: Int = 1)
class RepeatRule : TestRule {
private class RepeatStatement(private val statement: Statement, private val repeat: Int) : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
for (i in 0 until repeat) {
statement.evaluate()
}
}
}
override fun apply(statement: Statement, description: org.junit.runner.Description): Statement {
var result = statement
val repeat = description.getAnnotation(RepeatTest::class.java)
if (repeat != null) {
val times = repeat.value
result = RepeatStatement(statement, times)
}
return result
}
}
fun ViewInteraction.isDisplayed(): Boolean {
return try {
check(matches(ViewMatchers.isDisplayed()))
@ -38,15 +67,15 @@ fun ViewInteraction.isDisplayed(): Boolean {
* Doesn't work if the root changes (since it operates on the root!)
* @param viewId The id of the view to wait for.
*/
fun waitForView(viewId: Int) {
Espresso.onView(isRoot()).perform(waitForViewViewAction(viewId))
fun waitForView(viewId: Int, viewMatcher: Matcher<View> = withId(viewId)) {
Espresso.onView(isRoot()).perform(waitForViewViewAction(viewId, viewMatcher))
}
/**
* This ViewAction tells espresso to wait till a certain view is found in the view hierarchy.
* @param viewId The id of the view to wait for.
*/
private fun waitForViewViewAction(viewId: Int): ViewAction {
private fun waitForViewViewAction(viewId: Int, viewMatcher: Matcher<View>): ViewAction {
// The maximum time which espresso will wait for the view to show up (in milliseconds)
val timeOut = 5000
return object : ViewAction {
@ -62,7 +91,6 @@ private fun waitForViewViewAction(viewId: Int): ViewAction {
uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis()
val endTime = startTime + timeOut
val viewMatcher = withId(viewId)
do {
// Iterate through all views on the screen and see if the view we are looking for is there already
@ -75,7 +103,7 @@ private fun waitForViewViewAction(viewId: Int): ViewAction {
// Loops the main thread for a specified period of time.
// Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
uiController.loopMainThreadForAtLeast(100)
} while (System.currentTimeMillis() < endTime) // in case of a timeout we throw an exception -&gt; test fails
} while (System.currentTimeMillis() < endTime) // in case of a timeout we throw an exception - test fails
throw PerformException.Builder()
.withCause(TimeoutException())
.withActionDescription(this.description)
@ -271,6 +299,54 @@ fun clickChildViewWithId(id: Int) = object : ViewAction {
}
}
fun clickClickableSpan(textToClick: CharSequence): ViewAction? {
return object : ViewAction {
override fun getConstraints(): Matcher<View> {
return Matchers.instanceOf(TextView::class.java)
}
override fun getDescription(): String {
return "clicking on a ClickableSpan"
}
override fun perform(uiController: UiController?, view: View) {
val textView = view as TextView
val spannableString = textView.text as SpannableString
if (spannableString.isEmpty()) {
// TextView is empty, nothing to do
throw NoMatchingViewException.Builder()
.includeViewHierarchy(true)
.withRootView(textView)
.build()
}
// Get the links inside the TextView and check if we find textToClick
val spans = spannableString.getSpans(
0, spannableString.length,
ClickableSpan::class.java
)
if (spans.isNotEmpty()) {
var spanCandidate: ClickableSpan?
for (span in spans) {
spanCandidate = span
val start = spannableString.getSpanStart(spanCandidate)
val end = spannableString.getSpanEnd(spanCandidate)
val sequence = spannableString.subSequence(start, end)
if (textToClick.toString() == sequence.toString()) {
span.onClick(textView)
return
}
}
}
throw NoMatchingViewException.Builder()
.includeViewHierarchy(true)
.withRootView(textView)
.build()
}
}
}
fun typeTextInViewWithId(id: Int, text: String) = object : ViewAction {
override fun getConstraints() = null

View File

@ -1,8 +1,8 @@
package com.h.pixeldroid.testUtility
package org.pixeldroid.app.testUtility
import android.content.Context
import androidx.room.Room
import com.h.pixeldroid.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.AppDatabase
import org.ligi.tracedroid.TraceDroid

View File

@ -0,0 +1,20 @@
package org.pixeldroid.app.testUtility
/*
import android.os.Bundle
import androidx.test.runner.AndroidJUnitRunner
import com.linkedin.android.testbutler.TestButler
class TestRunner: AndroidJUnitRunner() {
override fun onStart() {
TestButler.setup(targetContext)
super.onStart()
}
override fun finish(resultCode: Int, results: Bundle) {
TestButler.teardown(targetContext)
super.finish(resultCode, results)
}
}
*/

View File

@ -1,8 +1,12 @@
package com.h.pixeldroid.testUtility
package org.pixeldroid.app.testUtility
import org.pixeldroid.app.BuildConfig.*
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
const val PACKAGE_ID = APPLICATION_ID
import com.h.pixeldroid.BuildConfig.*
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
val testiTestoInstance = InstanceDatabaseEntity(
uri = INSTANCE_URI,

View File

@ -0,0 +1,38 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="49.59793"
android:startX="42.9492"
android:endY="92.4963"
android:endX="85.84757"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<group
android:name="foo"
android:scaleX="2"
android:scaleY="2"
android:translateY="0"
android:translateX="0">
<path
android:pathData="M23.0332,30.2725L27.5781,30.2725C31.8595,30.2725 35.3302,26.9088 35.3302,22.7596C35.3302,18.6103 31.8595,15.2467 27.5781,15.2467L21.0185,15.2467C18.5485,15.2467 16.5461,17.1872 16.5461,19.581L16.5461,36.451L23.0332,30.2725Z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.h.pixeldroid">
package="org.pixeldroid.app">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -23,7 +23,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
<activity android:name="com.h.pixeldroid.postCreation.camera.CameraActivity" />
<activity android:name=".postCreation.camera.CameraActivity" />
<activity
android:name=".posts.ReportActivity"
android:screenOrientation="sensorPortrait"
@ -33,11 +33,26 @@
android:name=".postCreation.PostCreationActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="LockedOrientationActivity" />
tools:ignore="LockedOrientationActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name=".profile.FollowsActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" />
<activity
android:name=".posts.feeds.uncachedFeeds.hashtags.HashTagActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" />
<activity
android:name=".posts.PostActivity"
android:screenOrientation="sensorPortrait"
@ -66,7 +81,7 @@
<meta-data
android:name="android.app.default_searchable"
android:value=".searchDiscover.SearchActivity" />
android:value="org.pixeldroid.app.searchDiscover.SearchActivity" />
</activity>
<activity
android:name=".LoginActivity"
@ -116,7 +131,7 @@
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.h.pixeldroid.fileprovider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

View File

@ -818,9 +818,9 @@
</div>
<div class="library">
<!-- https://opensource.org/licenses/Apache-2.0 -->
<h1 class="title">kotlin-android-extensions-runtime</h1>
<p class="notice">Copyright &copy; JetBrains s.r.o. and contributors. All rights reserved.</p>
<p><a href="https://kotlinlang.org/">https://kotlinlang.org/</a></p>
<h1 class="title">startup-runtime</h1>
<p class="notice">Copyright &copy; Google Inc. All rights reserved.</p>
<p><a href="https://developer.android.com/jetpack/androidx/releases/startup#1.0.0">https://developer.android.com/jetpack/androidx/releases/startup#1.0.0</a></p>
<input type="checkbox"><label></label>
<div class="license">
<h2>

View File

@ -1,34 +0,0 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import com.h.pixeldroid.utils.api.objects.FeedContent
import kotlinx.coroutines.flow.Flow
/**
* ViewModel for the uncached feeds.
* The ViewModel works with the different [UncachedContentRepository]s to get the data.
*/
class FeedViewModel<T: FeedContent>(private val repository: UncachedContentRepository<T>) : ViewModel() {
private var currentResult: Flow<PagingData<T>>? = null
fun flow(): Flow<PagingData<T>> {
val lastResult = currentResult
if (lastResult != null) {
return lastResult
}
val newResult: Flow<PagingData<T>> = repository.getStream()
.cachedIn(viewModelScope)
currentResult = newResult
return newResult
}
}
/**
* Common interface for the different uncached feeds
*/
interface UncachedContentRepository<T: FeedContent>{
fun getStream(): Flow<PagingData<T>>
}

View File

@ -1,88 +0,0 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.search
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R
import com.h.pixeldroid.posts.StatusViewHolder
import com.h.pixeldroid.posts.feeds.uncachedFeeds.*
import com.h.pixeldroid.utils.api.objects.Results
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.utils.displayDimensionsInPx
/**
* Fragment to show a list of [Status]es, as a result of a search.
*/
class SearchPostsFragment : UncachedFeedFragment<Status>() {
private lateinit var query: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = PostsAdapter(requireContext().displayDimensionsInPx())
query = arguments?.getSerializable("searchFeed") as String
}
@ExperimentalPagingApi
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory(
SearchContentRepository<Status>(
apiHolder.setDomainToCurrentUser(db),
Results.SearchType.statuses,
db.userDao().getActiveUser()!!.accessToken,
query
)
)
)
.get(FeedViewModel::class.java) as FeedViewModel<Status>
launch()
initSearch()
return view
}
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
object : DiffUtil.ItemCallback<Status>() {
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
oldItem.id == newItem.id
}
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return StatusViewHolder.create(parent)
}
override fun getItemViewType(position: Int): Int {
return R.layout.post_fragment
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status
uiModel.let {
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope, displayDimensionsInPx)
}
}
}
}

View File

@ -1,5 +0,0 @@
package com.h.pixeldroid.utils.api.objects
data class Error(
val error: String?
)

View File

@ -1,18 +0,0 @@
package com.h.pixeldroid.utils.api.objects
import java.io.Serializable
data class Relationship(
// Required atributes
val id: String,
val following: Boolean,
val requested: Boolean,
val endorsed: Boolean,
val followed_by: Boolean,
val muting: Boolean,
val muting_notifications: Boolean,
val showing_reblogs: Boolean,
val blocking: Boolean,
val domain_blocking: Boolean,
val blocked_by: Boolean
) : Serializable

View File

@ -1,15 +0,0 @@
package com.h.pixeldroid.utils.api.objects
import java.io.Serializable
data class Tag(
//Base attributes
val name: String,
val url: String,
//Optional attributes
val history: List<History>? = emptyList()) : Serializable, FeedContent {
//needed to be a FeedContent, this inheritance is a bit fickle. Do not use.
override val id: String
get() = "tag"
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.app.AlertDialog
import android.content.Context
@ -9,12 +9,12 @@ import android.os.Bundle
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.databinding.ActivityLoginBinding
import com.h.pixeldroid.utils.*
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.*
import com.h.pixeldroid.utils.db.addUser
import com.h.pixeldroid.utils.db.storeInstance
import org.pixeldroid.app.databinding.ActivityLoginBinding
import org.pixeldroid.app.utils.*
import org.pixeldroid.app.utils.api.PixelfedAPI
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 retrofit2.HttpException
import java.io.IOException
@ -25,14 +25,13 @@ Overview of the flow of the login process: (boxes are requests done in parallel,
since they do not depend on each other)
_________________________________
|[PixelfedAPI.registerApplicationAsync]|
|[PixelfedAPI.registerApplication]|
|[PixelfedAPI.wellKnownNodeInfo] |
̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅ +----> [PixelfedAPI.nodeInfoSchema]
+----> [promptOAuth]
+---->____________________________
|[PixelfedAPI.instance] |
|[PixelfedAPI.obtainToken] |
̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅ +----> [PixelfedAPI.verifyCredentials]
̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅
+----> [PixelfedAPI.nodeInfoSchema] (and then [PixelfedAPI.instance] if needed)
+----> [promptOAuth]
+----> [PixelfedAPI.obtainToken]
+----> [PixelfedAPI.verifyCredentials]
*/
@ -311,7 +310,7 @@ class LoginActivity : BaseActivity() {
clientId = clientId,
clientSecret = clientSecret
)
apiHolder.setDomainToCurrentUser(db)
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)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid
package org.pixeldroid.app
import android.content.Context
import android.content.Intent
@ -14,22 +14,12 @@ import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.h.pixeldroid.databinding.ActivityMainBinding
import com.h.pixeldroid.postCreation.camera.CameraFragment
import com.h.pixeldroid.posts.feeds.cachedFeeds.notifications.NotificationsFragment
import com.h.pixeldroid.posts.feeds.cachedFeeds.postFeeds.PostFeedFragment
import com.h.pixeldroid.profile.ProfileActivity
import com.h.pixeldroid.searchDiscover.SearchDiscoverFragment
import com.h.pixeldroid.settings.SettingsActivity
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.db.addUser
import com.h.pixeldroid.utils.db.entities.HomeStatusDatabaseEntity
import com.h.pixeldroid.utils.db.entities.PublicFeedStatusDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.utils.hasInternet
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.materialdrawer.iconics.iconicsIcon
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
@ -40,9 +30,25 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.widget.AccountHeaderView
import org.ligi.tracedroid.sending.sendTraceDroidStackTracesIfExist
import org.pixeldroid.app.databinding.ActivityMainBinding
import org.pixeldroid.app.postCreation.camera.CameraFragment
import org.pixeldroid.app.posts.NestedScrollableHost
import org.pixeldroid.app.posts.feeds.cachedFeeds.CachedFeedFragment
import org.pixeldroid.app.posts.feeds.cachedFeeds.notifications.NotificationsFragment
import org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds.PostFeedFragment
import org.pixeldroid.app.profile.ProfileActivity
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.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 retrofit2.HttpException
import java.io.IOException
class MainActivity : BaseActivity() {
private lateinit var header: AccountHeaderView
@ -72,23 +78,22 @@ class MainActivity : BaseActivity() {
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
setupDrawer()
val tabs: List<() -> Fragment> = listOf(
{
PostFeedFragment<HomeStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", true) }
}
},
{ SearchDiscoverFragment() },
{ CameraFragment() },
{ NotificationsFragment() },
{
PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", false) }
}
}
{
PostFeedFragment<HomeStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", true) }
}
},
{ SearchDiscoverFragment() },
{ CameraFragment() },
{ NotificationsFragment() },
{
PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", false) }
}
}
)
setupTabs(tabs)
}
@ -177,7 +182,7 @@ class MainActivity : BaseActivity() {
} else {
val newActive = remainingUsers.first()
db.userDao().activateUser(newActive.user_id)
apiHolder.setDomainToCurrentUser(db)
apiHolder.setToCurrentUser()
//relaunch the app
launchActivity(MainActivity(), firstTime = true)
}
@ -185,16 +190,17 @@ class MainActivity : BaseActivity() {
}
private fun getUpdatedAccount() {
if (hasInternet(applicationContext)) {
val domain = user?.instance_uri.orEmpty()
val accessToken = user?.accessToken.orEmpty()
val refreshToken = user?.refreshToken
val clientId = user?.clientId.orEmpty()
val clientSecret = user?.clientSecret.orEmpty()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
lifecycleScope.launchWhenCreated {
try {
val account = api.verifyCredentials("Bearer $accessToken")
val domain = user?.instance_uri.orEmpty()
val accessToken = user?.accessToken.orEmpty()
val refreshToken = user?.refreshToken
val clientId = user?.clientId.orEmpty()
val clientSecret = user?.clientSecret.orEmpty()
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val account = api.verifyCredentials()
addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
fillDrawerAccountInfo(account.id!!)
} catch (exception: IOException) {
@ -220,7 +226,7 @@ class MainActivity : BaseActivity() {
db.userDao().deActivateActiveUsers()
db.userDao().activateUser(profile.identifier.toString())
apiHolder.setDomainToCurrentUser(db)
apiHolder.setToCurrentUser()
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
@ -268,8 +274,22 @@ class MainActivity : BaseActivity() {
header.setActiveProfile(account.toLong())
}
/**
* Use reflection to make it a bit harder to swipe between tabs
*/
private fun ViewPager2.reduceDragSensitivity() {
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
recyclerViewField.isAccessible = true
val recyclerView = recyclerViewField.get(this) as RecyclerView
val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop")
touchSlopField.isAccessible = true
val touchSlop = touchSlopField.get(recyclerView) as Int
touchSlopField.set(recyclerView, touchSlop*NestedScrollableHost.touchSlopModifier)
}
private fun setupTabs(tab_array: List<() -> Fragment>){
binding.viewPager.reduceDragSensitivity()
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): Fragment {
return tab_array[position]()
@ -279,6 +299,20 @@ class MainActivity : BaseActivity() {
return tab_array.size
}
}
binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?){}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {
tab?.position?.let { position ->
val page =
//No clue why this works but it does. F to pay respects
supportFragmentManager.findFragmentByTag("f$position")
(page as? CachedFeedFragment<*>)?.onTabReClicked()
}
}
})
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.icon = ContextCompat.getDrawable(applicationContext,

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation
package org.pixeldroid.app.postCreation
import android.app.Activity
import android.app.AlertDialog
@ -16,29 +16,30 @@ import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import com.h.pixeldroid.MainActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
import com.h.pixeldroid.postCreation.camera.CameraActivity
import com.h.pixeldroid.postCreation.carousel.CarouselItem
import com.h.pixeldroid.postCreation.carousel.ImageCarousel
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Attachment
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityPostCreationBinding
import org.pixeldroid.app.postCreation.camera.CameraActivity
import org.pixeldroid.app.postCreation.carousel.CarouselItem
import org.pixeldroid.app.postCreation.carousel.ImageCarousel
import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
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
import java.io.FileNotFoundException
import java.io.IOException
import java.io.OutputStream
import java.text.SimpleDateFormat
@ -58,9 +59,6 @@ data class PhotoData(
class PostCreationActivity : BaseActivity() {
private lateinit var accessToken: String
private lateinit var pixelfedAPI: PixelfedAPI
private var user: UserDatabaseEntity? = null
private lateinit var instance: InstanceDatabaseEntity
@ -86,9 +84,6 @@ class PostCreationActivity : BaseActivity() {
// get image URIs
intent.clipData?.let { addPossibleImages(it) }
accessToken = user?.accessToken.orEmpty()
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val carousel: ImageCarousel = binding.carousel
carousel.addData(photoData.map { CarouselItem(it.imageUri) })
carousel.layoutCarouselCallback = {
@ -105,7 +100,7 @@ class PostCreationActivity : BaseActivity() {
addPhoto()
}
carousel.updateDescriptionCallback = { position: Int, description: String ->
photoData[position].imageDescription = description
photoData.getOrNull(position)?.imageDescription = description
}
// get the description and send the post
@ -319,7 +314,16 @@ class PostCreationActivity : BaseActivity() {
for (data: PhotoData in photoData) {
val imageUri = data.imageUri
val imageInputStream = contentResolver.openInputStream(imageUri)!!
val imageInputStream = try {
contentResolver.openInputStream(imageUri)!!
} catch (e: FileNotFoundException){
AlertDialog.Builder(this).apply {
setMessage(getString(R.string.file_not_found).format(imageUri))
setNegativeButton(android.R.string.ok) { _, _ -> }
}.show()
return
}
val imagePart = ProgressRequestBody(imageInputStream, data.size)
val requestBody = MultipartBody.Builder()
@ -339,8 +343,8 @@ class PostCreationActivity : BaseActivity() {
val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) }
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", description, requestBody.parts[0])
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val inter = api.mediaUpload(description, requestBody.parts[0])
postSub = inter
.subscribeOn(Schedulers.io())
@ -382,8 +386,9 @@ class PostCreationActivity : BaseActivity() {
enableButton(false)
lifecycleScope.launchWhenCreated {
try {
pixelfedAPI.postStatus(
authorization = "Bearer $accessToken",
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
api.postStatus(
statusText = description,
media_ids = photoData.mapNotNull { it.uploadId }.toList()
)
@ -418,18 +423,18 @@ class PostCreationActivity : BaseActivity() {
}
private fun editResultContract(position: Int) = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
private val editResultContract: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
result: ActivityResult? ->
if (result?.resultCode == Activity.RESULT_OK && result.data != null) {
photoData[position].apply {
imageUri = result.data!!.getStringExtra("result")!!.toUri()
val position: Int = result.data!!.getIntExtra(PhotoEditActivity.PICTURE_POSITION, 0)
photoData.getOrNull(position)?.apply {
imageUri = result.data!!.getStringExtra(PhotoEditActivity.PICTURE_URI)!!.toUri()
size = imageUri.getSize()
}
progress = null
uploadId = null
} ?: Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
photoData[position].progress = null
photoData[position].uploadId = null
} else if(result?.resultCode != Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
}
@ -437,8 +442,8 @@ class PostCreationActivity : BaseActivity() {
private fun edit(position: Int) {
val intent = Intent(this, PhotoEditActivity::class.java)
.putExtra("picture_uri", photoData[position].imageUri)
.putExtra("no upload", false)
editResultContract(position).launch(intent)
.putExtra(PhotoEditActivity.PICTURE_URI, photoData[position].imageUri)
.putExtra(PhotoEditActivity.PICTURE_POSITION, position)
editResultContract.launch(intent)
}
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation
package org.pixeldroid.app.postCreation
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation
package org.pixeldroid.app.postCreation
import android.content.Context
import android.util.AttributeSet

View File

@ -1,8 +1,8 @@
package com.h.pixeldroid.postCreation.camera
package org.pixeldroid.app.postCreation.camera
import android.os.Bundle
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.R
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.R
class CameraActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -20,9 +20,4 @@ class CameraActivity : BaseActivity() {
supportFragmentManager.beginTransaction()
.add(R.id.camera_activity_fragment, cameraFragment).commit()
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.camera
package org.pixeldroid.app.postCreation.camera
import android.Manifest
import android.app.Activity
@ -31,8 +31,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.h.pixeldroid.R
import com.h.pixeldroid.postCreation.PostCreationActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.postCreation.PostCreationActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.carousel
package org.pixeldroid.app.postCreation.carousel
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
@ -9,7 +9,7 @@ import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.h.pixeldroid.R
import org.pixeldroid.app.R
class CarouselAdapter(
@ -96,8 +96,10 @@ class CarouselAdapter(
}
fun updateDescription(position: Int, description: String) {
dataList[position] = dataList[position].copy(caption = description)
notifyItemChanged(position)
dataList.getOrNull(position)?.apply {
dataList[position] = copy(caption = description)
notifyItemChanged(position)
}
}
fun addAll(dataList: List<CarouselItem>) {

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.carousel
package org.pixeldroid.app.postCreation.carousel
import android.net.Uri

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.carousel
package org.pixeldroid.app.postCreation.carousel
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.carousel
package org.pixeldroid.app.postCreation.carousel
import android.content.Context
import android.graphics.Color
@ -15,8 +15,8 @@ import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.*
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ImageCarouselBinding
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ImageCarouselBinding
import me.relex.circleindicator.CircleIndicator2
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
@ -44,7 +44,6 @@ class ImageCarousel(
private lateinit var recyclerView: RecyclerView
private lateinit var tvCaption: TextView
private lateinit var editTextMediaDescription: EditText
private var snapHelper: SnapHelper = PagerSnapHelper()
var indicator: CircleIndicator2? = null
@ -292,9 +291,9 @@ class ImageCarousel(
if(layoutCarousel){
field = value
if(value) editTextMediaDescription.setText(currentDescription)
if(value) binding.editTextMediaDescription.setText(currentDescription)
else {
val description = editTextMediaDescription.text.toString()
val description = binding.editTextMediaDescription.text.toString()
currentDescription = description
adapter?.updateDescription(currentPosition, description)
updateDescriptionCallback?.invoke(currentPosition, description)
@ -339,7 +338,6 @@ class ImageCarousel(
recyclerView = binding.recyclerView
tvCaption = binding.tvCaption
editTextMediaDescription = binding.editTextMediaDescription
recyclerView.setHasFixedSize(true)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.carousel
package org.pixeldroid.app.postCreation.carousel
import android.content.Context
import android.util.DisplayMetrics

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.photoEdit
package org.pixeldroid.app.postCreation.photoEdit
import android.os.Bundle
import androidx.fragment.app.Fragment
@ -6,7 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import com.h.pixeldroid.R
import org.pixeldroid.app.R
class EditImageFragment : Fragment(), SeekBar.OnSeekBarChangeListener {

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.photoEdit
package org.pixeldroid.app.postCreation.photoEdit
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.photoEdit
package org.pixeldroid.app.postCreation.photoEdit
import android.graphics.Bitmap
import android.os.Bundle
@ -11,7 +11,7 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R
import org.pixeldroid.app.R
import com.zomato.photofilters.FilterPack
import com.zomato.photofilters.imageprocessors.Filter
import com.zomato.photofilters.utils.ThumbnailItem

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.photoEdit
package org.pixeldroid.app.postCreation.photoEdit
import android.content.Context
import android.util.AttributeSet

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.photoEdit
package org.pixeldroid.app.postCreation.photoEdit
import android.app.Activity
import android.app.AlertDialog
@ -18,14 +18,11 @@ import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ActivityPhotoEditBinding
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
import com.h.pixeldroid.postCreation.PostCreationActivity
import com.h.pixeldroid.utils.BaseActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityPhotoEditBinding
import org.pixeldroid.app.postCreation.PostCreationActivity
import org.pixeldroid.app.utils.BaseActivity
import com.yalantis.ucrop.UCrop
import com.zomato.photofilters.imageprocessors.Filter
import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter
@ -65,6 +62,8 @@ class PhotoEditActivity : BaseActivity() {
private lateinit var filterListFragment: FilterListFragment
private lateinit var editImageFragment: EditImageFragment
private var picturePosition: Int? = null
private var brightnessFinal = BRIGHTNESS_START
private var saturationFinal = SATURATION_START
private var contrastFinal = CONTRAST_START
@ -74,6 +73,9 @@ class PhotoEditActivity : BaseActivity() {
}
companion object{
internal const val PICTURE_URI = "picture_uri"
internal const val PICTURE_POSITION = "picture_position"
private var executor: ExecutorService = newSingleThreadExecutor()
private var future: Future<*>? = null
@ -97,7 +99,8 @@ class PhotoEditActivity : BaseActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
initialUri = intent.getParcelableExtra("picture_uri")
initialUri = intent.getParcelableExtra(PICTURE_URI)
picturePosition = intent.getIntExtra(PICTURE_POSITION, 0)
imageUri = initialUri
// Crop button on-click listener
@ -342,7 +345,8 @@ class PhotoEditActivity : BaseActivity() {
private fun sendBackImage(file: String) {
val intent = Intent(this, PostCreationActivity::class.java)
.apply {
putExtra("result", file)
putExtra(PICTURE_URI, file)
putExtra(PICTURE_POSITION, picturePosition)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.photoEdit
package org.pixeldroid.app.postCreation.photoEdit
import android.graphics.Rect
import android.view.View

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.postCreation.photoEdit
package org.pixeldroid.app.postCreation.photoEdit
import android.content.Context
import android.view.LayoutInflater
@ -7,8 +7,8 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ThumbnailListItemBinding
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ThumbnailListItemBinding
import com.zomato.photofilters.utils.ThumbnailItem
class ThumbnailAdapter (private val context: Context,

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts
package org.pixeldroid.app.posts
import android.text.TextPaint
import android.text.style.ClickableSpan

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts
package org.pixeldroid.app.posts
import android.content.Context
import android.os.Build
@ -12,13 +12,13 @@ import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.toSpanned
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope
import com.h.pixeldroid.R
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account.Companion.openAccountFromId
import com.h.pixeldroid.utils.api.objects.Mention
import kotlinx.coroutines.coroutineScope
import org.pixeldroid.app.R
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Account.Companion.openAccountFromId
import org.pixeldroid.app.utils.api.objects.Mention
import org.pixeldroid.app.utils.api.objects.Tag.Companion.openTag
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import java.net.URI
import java.net.URISyntaxException
import java.text.ParseException
@ -50,12 +50,11 @@ fun getDomain(urlString: String?): String {
}
fun parseHTMLText(
text : String,
mentions: List<Mention>?,
api : PixelfedAPI,
context: Context,
credential: String,
lifecycleScope: LifecycleCoroutineScope
text: String,
mentions: List<Mention>?,
apiHolder: PixelfedAPIHolder,
context: Context,
lifecycleScope: LifecycleCoroutineScope,
) : Spanned {
//Convert text to spannable
val content = fromHtml(text)
@ -76,7 +75,7 @@ fun parseHTMLText(
val tag = text.subSequence(1, text.length).toString()
customSpan = object : ClickableSpanNoUnderline() {
override fun onClick(widget: View) {
Toast.makeText(context, tag, Toast.LENGTH_SHORT).show()
openTag(context, tag)
}
}
@ -108,7 +107,8 @@ fun parseHTMLText(
Log.e("MENTION", "CLICKED")
//Retrieve the account for the given profile
lifecycleScope.launchWhenCreated {
openAccountFromId(accountId, api, context, credential)
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
openAccountFromId(accountId, api, context)
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.h.pixeldroid.posts
package org.pixeldroid.app.posts
import android.content.Context
import android.util.AttributeSet
@ -51,7 +51,7 @@ class NestedScrollableHost : ConstraintLayout {
return v as? ViewPager2
}
var doubleTapCallback: ((Unit) -> Unit)? = null
var doubleTapCallback: ((Boolean) -> Unit)? = null
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
@ -79,7 +79,7 @@ class NestedScrollableHost : ConstraintLayout {
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
doubleTapCallback?.invoke(Unit)
doubleTapCallback?.invoke(true)
}
// Early return if child can't scroll in same direction as parent
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
@ -94,10 +94,13 @@ class NestedScrollableHost : ConstraintLayout {
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f/ touchSlopModifier else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f/touchSlopModifier
if(dx.absoluteValue * .5f > touchSlop || scaledDy > touchSlop) doubleTapCallback?.invoke(false)
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
@ -114,4 +117,8 @@ class NestedScrollableHost : ConstraintLayout {
}
}
}
companion object {
const val touchSlopModifier = 2
}
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts
package org.pixeldroid.app.posts
import android.content.Context
import android.os.Bundle
@ -9,23 +9,22 @@ import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
import android.widget.LinearLayout
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ActivityPostBinding
import com.h.pixeldroid.databinding.CommentBinding
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Mention
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import com.h.pixeldroid.utils.displayDimensionsInPx
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityPostBinding
import org.pixeldroid.app.databinding.CommentBinding
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Mention
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import org.pixeldroid.app.utils.displayDimensionsInPx
import retrofit2.HttpException
import java.io.IOException
class PostActivity : BaseActivity() {
lateinit var domain : String
private lateinit var accessToken : String
private lateinit var binding: ActivityPostBinding
@ -45,17 +44,15 @@ class PostActivity : BaseActivity() {
val user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty()
accessToken = user?.accessToken.orEmpty()
supportActionBar?.title = getString(R.string.post_title).format(status.account?.getusername())
val holder = StatusViewHolder(binding.postFragmentSingle)
holder.bind(status, apiHolder.api!!, db, lifecycleScope, displayDimensionsInPx(), isActivity = true)
holder.bind(status, apiHolder, db, lifecycleScope, displayDimensionsInPx(), isActivity = true)
val credential = "Bearer $accessToken"
activateCommenter(credential)
activateCommenter()
if(viewComments || postComment){
//Scroll already down as much as possible (since comments are not loaded yet)
@ -68,19 +65,14 @@ class PostActivity : BaseActivity() {
}
// also retrieve comments if we're not posting the comment
if(!postComment) retrieveComments(apiHolder.api!!, credential)
if(!postComment) retrieveComments(apiHolder.api!!)
}
binding.postFragmentSingle.viewComments.setOnClickListener {
retrieveComments(apiHolder.api!!, credential)
retrieveComments(apiHolder.api!!)
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun activateCommenter(credential: String) {
private fun activateCommenter() {
//Activate commenter
binding.submitComment.setOnClickListener {
val textIn = binding.editComment.text
@ -94,15 +86,14 @@ class PostActivity : BaseActivity() {
} else {
//Post the comment
lifecycleScope.launchWhenCreated {
apiHolder.api?.let { it1 -> postComment(it1, credential) }
apiHolder.api?.let { it1 -> postComment(it1) }
}
}
}
}
private fun addComment(context: Context, commentContainer: LinearLayout,
commentUsername: String, commentContent: String, mentions: List<Mention>,
credential: String) {
commentUsername: String, commentContent: String, mentions: List<Mention>) {
val itemBinding = CommentBinding.inflate(
@ -111,27 +102,30 @@ class PostActivity : BaseActivity() {
itemBinding.user.text = commentUsername
itemBinding.commentText.text = parseHTMLText(
commentContent,
mentions,
apiHolder.api!!,
context,
credential,
lifecycleScope
commentContent,
mentions,
apiHolder,
context,
lifecycleScope
)
}
private fun retrieveComments(api: PixelfedAPI, credential: String) {
private fun retrieveComments(api: PixelfedAPI) {
lifecycleScope.launchWhenCreated {
status.id.let {
try {
val statuses = api.statusComments(it, credential).descendants
val statuses = api.statusComments(it).descendants
binding.commentContainer.removeAllViews()
//Create the new views for each comment
for (status in statuses) {
addComment(binding.root.context, binding.commentContainer, status.account!!.username!!,
status.content!!, status.mentions.orEmpty(), credential
addComment(
binding.root.context,
binding.commentContainer,
status.account!!.username!!,
status.content!!,
status.mentions.orEmpty()
)
}
binding.commentContainer.visibility = View.VISIBLE
@ -149,19 +143,18 @@ class PostActivity : BaseActivity() {
private suspend fun postComment(
api: PixelfedAPI,
credential: String,
) {
val textIn = binding.editComment.text
val nonNullText = textIn.toString()
status.id.let {
try {
val response = api.postStatus(credential, nonNullText, it)
val response = api.postStatus(nonNullText, it)
binding.commentIn.visibility = View.GONE
//Add the comment to the comment section
addComment(
binding.root.context, binding.commentContainer, response.account!!.username!!,
response.content!!, response.mentions.orEmpty(), credential
response.content!!, response.mentions.orEmpty()
)
Toast.makeText(

View File

@ -1,12 +1,12 @@
package com.h.pixeldroid.posts
package org.pixeldroid.app.posts
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ActivityReportBinding
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.api.objects.Status
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityReportBinding
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.api.objects.Status
import retrofit2.HttpException
import java.io.IOException
@ -24,10 +24,6 @@ class ReportActivity : BaseActivity() {
val status = intent.getSerializableExtra(Status.POST_TAG) as Status?
//get the currently active user
val user = db.userDao().getActiveUser()
binding.reportTargetTextview.text = getString(R.string.report_target).format(status?.account?.acct)
@ -37,12 +33,15 @@ class ReportActivity : BaseActivity() {
binding.textInputLayout.editText?.isEnabled = false
val accessToken = user?.accessToken.orEmpty()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
lifecycleScope.launchWhenCreated {
try {
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), binding.textInputLayout.editText?.text.toString())
api.report(
status?.account?.id!!,
listOf(status),
binding.textInputLayout.editText?.text.toString()
)
reportStatus(true)
} catch (exception: IOException) {
@ -67,9 +66,4 @@ class ReportActivity : BaseActivity() {
binding.reportProgressBar.visibility = View.GONE
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}

View File

@ -1,9 +1,10 @@
package com.h.pixeldroid.posts
package org.pixeldroid.app.posts
import android.Manifest
import android.app.AlertDialog
import android.content.Intent
import android.graphics.Typeface
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.text.method.LinkMovementMethod
import android.util.Log
@ -14,21 +15,23 @@ import android.widget.*
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.google.android.material.snackbar.Snackbar
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.AlbumImageViewBinding
import com.h.pixeldroid.databinding.PostFragmentBinding
import com.h.pixeldroid.utils.BlurHashDecoder
import com.h.pixeldroid.utils.ImageConverter
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Attachment
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import com.h.pixeldroid.utils.db.AppDatabase
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.AlbumImageViewBinding
import org.pixeldroid.app.databinding.PostFragmentBinding
import org.pixeldroid.app.utils.BlurHashDecoder
import org.pixeldroid.app.utils.ImageConverter
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Attachment
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import com.karumi.dexter.Dexter
import com.karumi.dexter.listener.PermissionDeniedResponse
import com.karumi.dexter.listener.PermissionGrantedResponse
@ -46,7 +49,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
private var status: Status? = null
fun bind(status: Status?, pixelfedAPI: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>, isActivity: Boolean = false) {
fun bind(status: Status?, pixelfedAPI: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>, isActivity: Boolean = false) {
this.itemView.visibility = View.VISIBLE
this.status = status
@ -177,21 +180,19 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
private fun setDescription(
api: PixelfedAPI,
credential: String,
lifecycleScope: LifecycleCoroutineScope
apiHolder: PixelfedAPIHolder,
lifecycleScope: LifecycleCoroutineScope,
) {
binding.description.apply {
if (status?.content.isNullOrBlank()) {
visibility = View.GONE
} else {
text = parseHTMLText(
status?.content.orEmpty(),
status?.mentions,
api,
binding.root.context,
credential,
lifecycleScope
status?.content.orEmpty(),
status?.mentions,
apiHolder,
binding.root.context,
lifecycleScope
)
movementMethod = LinkMovementMethod.getInstance()
}
@ -199,25 +200,20 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
//region buttons
private fun activateButtons(
api: PixelfedAPI,
apiHolder: PixelfedAPIHolder,
db: AppDatabase,
lifecycleScope: LifecycleCoroutineScope,
isActivity: Boolean
){
val user = db.userDao().getActiveUser()!!
val credential = "Bearer ${user.accessToken}"
//Set the special HTML text
setDescription(api, credential, lifecycleScope)
setDescription(apiHolder, lifecycleScope)
//Activate onclickListeners
activateLiker(
api, credential, status?.favourited ?: false,
lifecycleScope
apiHolder, status?.favourited ?: false, lifecycleScope
)
activateReblogger(
api, credential, status?.reblogged ?: false,
lifecycleScope
apiHolder, status?.reblogged ?: false, lifecycleScope
)
if(isActivity){
@ -237,14 +233,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
showComments(lifecycleScope, isActivity)
activateMoreButton(api, db, lifecycleScope)
activateMoreButton(apiHolder, db, lifecycleScope)
}
private fun activateReblogger(
api: PixelfedAPI,
credential: String,
isReblogged: Boolean,
lifecycleScope: LifecycleCoroutineScope
apiHolder: PixelfedAPIHolder,
isReblogged: Boolean,
lifecycleScope: LifecycleCoroutineScope,
) {
binding.reblogger.apply {
//Set initial button state
@ -253,12 +248,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate the button
setEventListener { _, buttonState ->
lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
if (buttonState) {
// Button is active
undoReblogPost(api, credential)
undoReblogPost(api)
} else {
// Button is inactive
reblogPost(api, credential)
reblogPost(api)
}
}
//show animation or not?
@ -267,15 +263,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private suspend fun reblogPost(
api: PixelfedAPI,
credential: String
) {
private suspend fun reblogPost(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.reblogStatus(credential, it)
val resp = api.reblogStatus(it)
//Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context)
@ -290,14 +283,11 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private suspend fun undoReblogPost(
api: PixelfedAPI,
credential: String,
) {
private suspend fun undoReblogPost(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.undoReblogStatus(credential, it)
val resp = api.undoReblogStatus(it)
//Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context)
@ -312,7 +302,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private fun activateMoreButton(api: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
private fun activateMoreButton(apiHolder: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
binding.statusMore.setOnClickListener {
PopupMenu(it.context, it).apply {
setOnMenuItemClickListener { item ->
@ -395,7 +385,8 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
db.homePostDao().delete(id, user.user_id, user.instance_uri)
db.publicPostDao().delete(id, user.user_id, user.instance_uri)
try {
api.deleteStatus("Bearer ${user.accessToken}", id)
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
api.deleteStatus(id)
binding.root.visibility = View.GONE
} catch (exception: HttpException) {
Toast.makeText(
@ -439,10 +430,9 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
private fun activateLiker(
api: PixelfedAPI,
credential: String,
isLiked: Boolean,
lifecycleScope: LifecycleCoroutineScope
apiHolder: PixelfedAPIHolder,
isLiked: Boolean,
lifecycleScope: LifecycleCoroutineScope,
) {
binding.liker.apply {
@ -452,12 +442,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate the liker
setEventListener { _, buttonState ->
lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
if (buttonState) {
// Button is active, unlike
unLikePostCall(api, credential)
unLikePostCall(api)
} else {
// Button is inactive, like
likePostCall(api, credential)
likePostCall(api)
}
}
//show animation or not?
@ -468,20 +459,23 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate double tap liking
var clicked = false
binding.postPagerHost.doubleTapCallback = {
lifecycleScope.launchWhenCreated {
if(!it) clicked = false
else lifecycleScope.launchWhenCreated {
//Check that the post isn't hidden
if(binding.sensitiveWarning.visibility == View.GONE) {
//Check for double click
if(clicked) {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
if (binding.liker.isChecked) {
// Button is active, unlike
binding.liker.isChecked = false
unLikePostCall(api, credential)
unLikePostCall(api)
} else {
// Button is inactive, like
binding.liker.playAnimation()
binding.liker.isChecked = true
likePostCall(api, credential)
binding.likeAnimation.animateView()
likePostCall(api)
}
} else {
clicked = true
@ -490,20 +484,27 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
binding.postPager.handler.postDelayed(fun() { clicked = false }, 500)
}
}
}
}
}
private fun ImageView.animateView() {
visibility = View.VISIBLE
when (val drawable = drawable) {
is AnimatedVectorDrawableCompat -> {
drawable.start()
}
is AnimatedVectorDrawable -> {
drawable.start()
}
}
}
private suspend fun likePostCall(
api: PixelfedAPI,
credential: String,
) {
private suspend fun likePostCall(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.likePost(credential, it)
val resp = api.likePost(it)
//Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context)
@ -518,15 +519,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private suspend fun unLikePostCall(
api: PixelfedAPI,
credential: String,
) {
private suspend fun unLikePostCall(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.unlikePost(credential, it)
val resp = api.unlikePost(it)
//Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds
package org.pixeldroid.app.posts.feeds
import android.view.LayoutInflater
import android.view.ViewGroup
@ -12,14 +12,16 @@ import androidx.paging.LoadStateAdapter
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ErrorLayoutBinding
import com.h.pixeldroid.databinding.LoadStateFooterViewItemBinding
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel
import com.h.pixeldroid.utils.api.objects.FeedContent
import com.google.gson.Gson
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ErrorLayoutBinding
import org.pixeldroid.app.databinding.LoadStateFooterViewItemBinding
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import org.pixeldroid.app.utils.api.objects.FeedContent
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import retrofit2.HttpException
/**
* Shows or hides the error in the different FeedFragments
@ -56,24 +58,17 @@ internal fun <T: Any> initAdapter(
if(!progressBar.isVisible && swipeRefreshLayout.isRefreshing) {
// Stop loading spinner when loading is done
swipeRefreshLayout.isRefreshing = loadState.refresh is LoadState.Loading
} else {
// ProgressBar should stop showing as soon as the source stops loading ("source"
// meaning the database, so don't wait on the network)
val sourceLoading = loadState.source.refresh is LoadState.Loading
if(!sourceLoading && recyclerView.size > 0){
recyclerView.isVisible = true
progressBar.isVisible = false
} else if(recyclerView.size == 0
&& loadState.append is LoadState.NotLoading
&& loadState.append.endOfPaginationReached){
progressBar.isVisible = false
showError(motionLayout = motionLayout, errorLayout = errorLayout,
errorText = errorLayout.root.context.getString(R.string.empty_feed))
}
}
// ProgressBar should stop showing as soon as the source stops loading ("source"
// meaning the database, so don't wait on the network)
val sourceLoading = loadState.source.refresh is LoadState.Loading
if (!sourceLoading && adapter.itemCount > 0) {
recyclerView.isVisible = true
progressBar.isVisible = false
}
// Toast on any error, regardless of whether it came from RemoteMediator or PagingSource
// Show any error, regardless of whether it came from RemoteMediator or PagingSource
val errorState = loadState.source.append as? LoadState.Error
?: loadState.source.prepend as? LoadState.Error
?: loadState.source.refresh as? LoadState.Error
@ -81,21 +76,37 @@ internal fun <T: Any> initAdapter(
?: loadState.prepend as? LoadState.Error
?: loadState.refresh as? LoadState.Error
errorState?.let {
showError(motionLayout = motionLayout, errorLayout = errorLayout, errorText = it.error.toString())
val error: String = (it.error as? HttpException)?.response()?.errorBody()?.string()?.ifEmpty { null }?.let { s ->
Gson().fromJson(s, org.pixeldroid.app.utils.api.objects.Error::class.java)?.error?.ifBlank { null }
} ?: it.error.localizedMessage.orEmpty()
showError(motionLayout = motionLayout, errorLayout = errorLayout, errorText = error)
}
// If the state is not an error, hide the error layout, or show message that the feed is empty
if(errorState == null) {
showError(motionLayout = motionLayout, errorLayout = errorLayout, show = false, errorText = "")
if (adapter.itemCount == 0
&& loadState.append is LoadState.NotLoading
&& loadState.append.endOfPaginationReached
) {
progressBar.isVisible = false
showError(
motionLayout = motionLayout, errorLayout = errorLayout,
errorText = errorLayout.root.context.getString(R.string.empty_feed)
)
} else {
showError(motionLayout = motionLayout, errorLayout = errorLayout, show = false, errorText = "")
}
}
}
}
fun launch(
job: Job?, lifecycleScope: LifecycleCoroutineScope, viewModel: FeedViewModel<FeedContent>,
pagingDataAdapter: PagingDataAdapter<FeedContent, RecyclerView.ViewHolder>): Job {
fun <T: FeedContent> launch(
job: Job?, lifecycleScope: LifecycleCoroutineScope, viewModel: FeedViewModel<T>,
pagingDataAdapter: PagingDataAdapter<T, RecyclerView.ViewHolder>): Job {
// Make sure we cancel the previous job before creating a new one
job?.cancel()
return lifecycleScope.launch {
viewModel.flow().collectLatest {
viewModel.flow.collectLatest {
pagingDataAdapter.submitData(it)
}
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds.cachedFeeds
package org.pixeldroid.app.posts.feeds.cachedFeeds
import android.os.Bundle
import android.view.LayoutInflater
@ -8,21 +8,18 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.*
import androidx.paging.LoadState.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import com.h.pixeldroid.databinding.FragmentFeedBinding
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.dao.feedContent.FeedContentDao
import com.h.pixeldroid.utils.BaseFragment
import com.h.pixeldroid.posts.feeds.initAdapter
import com.h.pixeldroid.utils.api.objects.FeedContentDatabase
import kotlinx.coroutines.flow.*
import org.pixeldroid.app.databinding.FragmentFeedBinding
import org.pixeldroid.app.posts.feeds.initAdapter
import org.pixeldroid.app.utils.BaseFragment
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
import org.pixeldroid.app.utils.limitedLengthSmoothScrollToPosition
/**
* A fragment representing a list of [FeedContentDatabase] items that are cached by the database.
@ -43,7 +40,7 @@ open class CachedFeedFragment<T: FeedContentDatabase> : BaseFragment() {
// Make sure we cancel the previous job before creating a new one
job?.cancel()
job = lifecycleScope.launchWhenStarted {
viewModel.flow().collectLatest {
viewModel.flow.collectLatest {
adapter.submitData(it)
}
}
@ -51,12 +48,14 @@ open class CachedFeedFragment<T: FeedContentDatabase> : BaseFragment() {
internal fun initSearch() {
// Scroll to top when the list is refreshed from network.
lifecycleScope.launch {
lifecycleScope.launchWhenStarted {
adapter.loadStateFlow
// Only emit when REFRESH LoadState for RemoteMediator changes.
.distinctUntilChangedBy { it.refresh }
.distinctUntilChangedBy {
it.refresh
}
// Only react to cases where Remote REFRESH completes i.e., NotLoading.
.filter { it.refresh is LoadState.NotLoading }
.filter { it.refresh is NotLoading}
.collect { binding.list.scrollToPosition(0) }
}
}
@ -73,17 +72,16 @@ open class CachedFeedFragment<T: FeedContentDatabase> : BaseFragment() {
initAdapter(binding.progressBar, binding.swipeRefreshLayout,
binding.list, binding.motionLayout, binding.errorLayout, adapter)
//binding.progressBar.visibility = View.GONE
binding.swipeRefreshLayout.setOnRefreshListener {
//It shouldn't be necessary to also retry() in addition to refresh(),
//but if we don't do this, reloads after an error fail immediately...
// https://issuetracker.google.com/issues/173438474
adapter.retry()
adapter.refresh()
}
return binding.root
}
fun onTabReClicked() {
binding.list.limitedLengthSmoothScrollToPosition(0)
}
}

View File

@ -14,12 +14,12 @@
* limitations under the License.
*/
package com.h.pixeldroid.posts.feeds.cachedFeeds
package org.pixeldroid.app.posts.feeds.cachedFeeds
import androidx.paging.*
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.dao.feedContent.FeedContentDao
import com.h.pixeldroid.utils.api.objects.FeedContentDatabase
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

View File

@ -14,31 +14,20 @@
* limitations under the License.
*/
package com.h.pixeldroid.posts.feeds.cachedFeeds
package org.pixeldroid.app.posts.feeds.cachedFeeds
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import com.h.pixeldroid.utils.api.objects.FeedContentDatabase
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import kotlinx.coroutines.flow.Flow
/**
* ViewModel for the cached feeds.
* The ViewModel works with the [FeedContentRepository] to get the data.
*/
class FeedViewModel<T: FeedContentDatabase>(private val repository: FeedContentRepository<T>) : ViewModel() {
private var currentResult: Flow<PagingData<T>>? = null
class FeedViewModel<T: FeedContentDatabase>(repository: FeedContentRepository<T>) : ViewModel() {
@ExperimentalPagingApi
fun flow(): Flow<PagingData<T>> {
val lastResult = currentResult
if (lastResult != null) {
return lastResult
}
val newResult: Flow<PagingData<T>> = repository.stream()
.cachedIn(viewModelScope)
currentResult = newResult
return newResult
}
val flow: Flow<PagingData<T>> = repository.stream().cachedIn(viewModelScope)
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds.cachedFeeds.notifications
package org.pixeldroid.app.posts.feeds.cachedFeeds.notifications
import android.content.Context
import android.content.Intent
@ -18,21 +18,19 @@ import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.FragmentNotificationsBinding
import com.h.pixeldroid.posts.PostActivity
import com.h.pixeldroid.posts.feeds.cachedFeeds.CachedFeedFragment
import com.h.pixeldroid.posts.feeds.cachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.cachedFeeds.ViewModelFactory
import com.h.pixeldroid.posts.parseHTMLText
import com.h.pixeldroid.posts.setTextViewFromISO8601
import com.h.pixeldroid.profile.ProfileActivity
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Notification
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.FragmentNotificationsBinding
import org.pixeldroid.app.posts.PostActivity
import org.pixeldroid.app.posts.feeds.cachedFeeds.CachedFeedFragment
import org.pixeldroid.app.posts.feeds.cachedFeeds.FeedViewModel
import org.pixeldroid.app.posts.feeds.cachedFeeds.ViewModelFactory
import org.pixeldroid.app.posts.parseHTMLText
import org.pixeldroid.app.posts.setTextViewFromISO8601
import org.pixeldroid.app.profile.ProfileActivity
import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.api.objects.Notification
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
/**
@ -42,7 +40,7 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = NotificationsAdapter(apiHolder, db)
adapter = NotificationsAdapter(apiHolder)
}
@ExperimentalPagingApi
@ -55,10 +53,10 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(
this,
requireActivity(),
ViewModelFactory(db, db.notificationDao(), NotificationsRemoteMediator(apiHolder, db))
)
.get(FeedViewModel::class.java) as FeedViewModel<Notification>
.get("notifications", FeedViewModel::class.java) as FeedViewModel<Notification>
launch()
initSearch()
@ -165,9 +163,8 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
fun bind(
notification: Notification?,
api: PixelfedAPI,
accessToken: String,
lifecycleScope: LifecycleCoroutineScope
api: PixelfedAPIHolder,
lifecycleScope: LifecycleCoroutineScope,
) {
this.notification = notification
@ -204,12 +201,11 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
//Convert HTML to clickable text
postDescription.text =
parseHTMLText(
notification?.status?.content ?: "",
notification?.status?.mentions,
api,
itemView.context,
"Bearer $accessToken",
lifecycleScope
notification?.status?.content ?: "",
notification?.status?.mentions,
api,
itemView.context,
lifecycleScope
)
}
@ -226,7 +222,6 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
inner class NotificationsAdapter(
private val apiHolder: PixelfedAPIHolder,
private val db: AppDatabase
) : PagingDataAdapter<Notification, RecyclerView.ViewHolder>(
object : DiffUtil.ItemCallback<Notification>() {
override fun areItemsTheSame(
@ -256,10 +251,9 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
val uiModel = getItem(position)
uiModel.let {
(holder as NotificationViewHolder).bind(
it,
apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
lifecycleScope
it,
apiHolder,
lifecycleScope
)
}
}

View File

@ -14,13 +14,13 @@
* limitations under the License.
*/
package com.h.pixeldroid.posts.feeds.cachedFeeds.notifications
package org.pixeldroid.app.posts.feeds.cachedFeeds.notifications
import androidx.paging.*
import androidx.room.withTransaction
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import com.h.pixeldroid.utils.api.objects.Notification
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.api.objects.Notification
import retrofit2.HttpException
import java.io.IOException
import java.lang.NullPointerException
@ -58,13 +58,12 @@ class NotificationsRemoteMediator @Inject constructor(
try {
val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val accessToken = user.accessToken
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val apiResponse = api.notifications("Bearer $accessToken",
max_id = max_id,
min_id = min_id,
limit = state.config.pageSize.toString(),
val apiResponse = api.notifications(
max_id = max_id,
min_id = min_id,
limit = state.config.pageSize.toString(),
)
apiResponse.forEach{it.user_id = user.user_id; it.instance_uri = user.instance_uri}

View File

@ -1,10 +1,10 @@
package com.h.pixeldroid.posts.feeds.cachedFeeds.postFeeds
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
import androidx.paging.*
import androidx.room.withTransaction
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import com.h.pixeldroid.utils.db.entities.HomeStatusDatabaseEntity
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
import retrofit2.HttpException
import java.io.IOException
import java.lang.NullPointerException
@ -43,12 +43,12 @@ class HomeFeedRemoteMediator @Inject constructor(
try {
val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val accessToken = user.accessToken
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val apiResponse = api.timelineHome( "Bearer $accessToken",
max_id= max_id, min_id = min_id,
limit = state.config.pageSize.toString())
val apiResponse = api.timelineHome(
max_id= max_id,
min_id = min_id, limit = state.config.pageSize.toString()
)
val dbObjects = apiResponse.map{
HomeStatusDatabaseEntity(user.user_id, user.instance_uri, it)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds.cachedFeeds.postFeeds
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
import android.os.Bundle
import android.view.LayoutInflater
@ -11,15 +11,16 @@ import androidx.paging.PagingDataAdapter
import androidx.paging.RemoteMediator
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R
import com.h.pixeldroid.utils.db.dao.feedContent.FeedContentDao
import com.h.pixeldroid.posts.StatusViewHolder
import com.h.pixeldroid.posts.feeds.cachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.cachedFeeds.CachedFeedFragment
import com.h.pixeldroid.posts.feeds.cachedFeeds.ViewModelFactory
import com.h.pixeldroid.utils.api.objects.FeedContentDatabase
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.utils.displayDimensionsInPx
import org.pixeldroid.app.R
import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
import org.pixeldroid.app.posts.StatusViewHolder
import org.pixeldroid.app.posts.feeds.cachedFeeds.FeedViewModel
import org.pixeldroid.app.posts.feeds.cachedFeeds.CachedFeedFragment
import org.pixeldroid.app.posts.feeds.cachedFeeds.ViewModelFactory
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.displayDimensionsInPx
import kotlin.properties.Delegates
/**
@ -32,14 +33,17 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
private lateinit var mediator: RemoteMediator<Int, T>
private lateinit var dao: FeedContentDao<T>
private var home by Delegates.notNull<Boolean>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = PostsAdapter(requireContext().displayDimensionsInPx())
home = requireArguments().get("home") as Boolean
@Suppress("UNCHECKED_CAST")
if (requireArguments().get("home") as Boolean){
if (home){
mediator = HomeFeedRemoteMediator(apiHolder, db) as RemoteMediator<Int, T>
dao = db.homePostDao() as FeedContentDao<T>
}
@ -59,8 +63,8 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory(db, dao, mediator))
.get(FeedViewModel::class.java) as FeedViewModel<T>
viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(db, dao, mediator))
.get(if(home) "home" else "public", FeedViewModel::class.java) as FeedViewModel<T>
launch()
initSearch()
@ -70,12 +74,8 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<T, RecyclerView.ViewHolder>(
object : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean =
oldItem.id == newItem.id
override fun areItemsTheSame (oldItem: T, newItem: T): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = oldItem.id == newItem.id
}
) {
@ -88,9 +88,9 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status
uiModel.let {
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope, displayDimensionsInPx)
val uiModel = getItem(position) as Status?
uiModel?.let {
(holder as StatusViewHolder).bind(it, apiHolder, db, lifecycleScope, displayDimensionsInPx)
}
}
}

View File

@ -14,13 +14,13 @@
* limitations under the License.
*/
package com.h.pixeldroid.posts.feeds.cachedFeeds.postFeeds
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
import androidx.paging.*
import androidx.room.withTransaction
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.PublicFeedStatusDatabaseEntity
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import retrofit2.HttpException
import java.io.IOException
import java.lang.NullPointerException
@ -58,7 +58,7 @@ class PublicFeedRemoteMediator @Inject constructor(
try {
val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val apiResponse = api.timelinePublic(
max_id = max_id,

View File

@ -0,0 +1,22 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import org.pixeldroid.app.utils.api.objects.FeedContent
import kotlinx.coroutines.flow.Flow
/**
* ViewModel for the uncached feeds.
* The ViewModel works with the different [UncachedContentRepository]s to get the data.
*/
class FeedViewModel<T: FeedContent>(repository: UncachedContentRepository<T>) : ViewModel() {
val flow: Flow<PagingData<T>> = repository.getStream().cachedIn(viewModelScope)
}
/**
* Common interface for the different uncached feeds
*/
interface UncachedContentRepository<T: FeedContent>{
fun getStream(): Flow<PagingData<T>>
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds
package org.pixeldroid.app.posts.feeds.uncachedFeeds
import android.os.Bundle
import android.view.LayoutInflater
@ -9,17 +9,17 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.*
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.posts.feeds.launch
import org.pixeldroid.app.posts.feeds.launch
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import com.h.pixeldroid.databinding.FragmentFeedBinding
import com.h.pixeldroid.utils.BaseFragment
import com.h.pixeldroid.posts.feeds.initAdapter
import com.h.pixeldroid.utils.api.objects.FeedContent
import org.pixeldroid.app.databinding.FragmentFeedBinding
import org.pixeldroid.app.utils.BaseFragment
import org.pixeldroid.app.posts.feeds.initAdapter
import org.pixeldroid.app.utils.api.objects.FeedContent
/**
@ -37,10 +37,7 @@ open class UncachedFeedFragment<T: FeedContent> : BaseFragment() {
internal fun launch() {
@Suppress("UNCHECKED_CAST")
job = launch(job, lifecycleScope,
viewModel as FeedViewModel<FeedContent>,
adapter as PagingDataAdapter<FeedContent, RecyclerView.ViewHolder>)
job = launch(job, lifecycleScope, viewModel, adapter)
}
internal fun initSearch() {
@ -68,9 +65,6 @@ open class UncachedFeedFragment<T: FeedContent> : BaseFragment() {
binding.motionLayout, binding.errorLayout, adapter)
binding.swipeRefreshLayout.setOnRefreshListener {
//It shouldn't be necessary to also retry() in addition to refresh(),
//but if we don't do this, reloads after an error fail immediately...
adapter.retry()
adapter.refresh()
}

View File

@ -0,0 +1,103 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.pixeldroid.app.R
import org.pixeldroid.app.posts.StatusViewHolder
import org.pixeldroid.app.posts.feeds.uncachedFeeds.hashtags.HashTagContentRepository
import org.pixeldroid.app.posts.feeds.uncachedFeeds.search.SearchContentRepository
import org.pixeldroid.app.utils.api.objects.Results
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.api.objects.Tag.Companion.HASHTAG_TAG
import org.pixeldroid.app.utils.displayDimensionsInPx
/**
* Fragment to show a list of [Status]es, as a result of a search or a hashtag.
*/
class UncachedPostsFragment : UncachedFeedFragment<Status>() {
private var hashtagOrQuery: String? = null
private var search: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = PostsAdapter(requireContext().displayDimensionsInPx())
hashtagOrQuery = arguments?.getString(HASHTAG_TAG)
if(hashtagOrQuery == null){
search = true
hashtagOrQuery = arguments?.getString("searchFeed")!!
}
}
@ExperimentalPagingApi
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = if(search) {
ViewModelProvider(
requireActivity(), ViewModelFactory(
SearchContentRepository<Status>(
apiHolder.setToCurrentUser(),
Results.SearchType.statuses,
hashtagOrQuery!!
)
)
)
.get("searchPosts", FeedViewModel::class.java) as FeedViewModel<Status>
} else {
ViewModelProvider(requireActivity(), ViewModelFactory(
HashTagContentRepository(
apiHolder.setToCurrentUser(),
hashtagOrQuery!!
)
)
)
.get(HASHTAG_TAG, FeedViewModel::class.java) as FeedViewModel<Status>
}
launch()
initSearch()
return view
}
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
object : DiffUtil.ItemCallback<Status>() {
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
oldItem.id == newItem.id
}
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return StatusViewHolder.create(parent)
}
override fun getItemViewType(position: Int): Int = R.layout.post_fragment
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
getItem(position)?.let {
(holder as StatusViewHolder).bind(it, apiHolder, db, lifecycleScope, displayDimensionsInPx)
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists
package org.pixeldroid.app.posts.feeds.uncachedFeeds.accountLists
import android.annotation.SuppressLint
import android.os.Bundle
@ -13,14 +13,14 @@ import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.AccountListEntryBinding
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.ViewModelFactory
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG
import com.h.pixeldroid.utils.api.objects.Account.Companion.FOLLOWERS_TAG
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.AccountListEntryBinding
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedFeedFragment
import org.pixeldroid.app.posts.feeds.uncachedFeeds.ViewModelFactory
import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG
import org.pixeldroid.app.utils.api.objects.Account.Companion.FOLLOWERS_TAG
/**
@ -52,16 +52,15 @@ class AccountListFragment : UncachedFeedFragment<Account>() {
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory(
viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
FollowersContentRepository(
apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
apiHolder.setToCurrentUser(),
id,
following
)
)
)
.get(FeedViewModel::class.java) as FeedViewModel<Account>
.get("accountList", FeedViewModel::class.java) as FeedViewModel<Account>
launch()
initSearch()

View File

@ -1,12 +1,12 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists
package org.pixeldroid.app.posts.feeds.uncachedFeeds.accountLists
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.utils.api.objects.Account
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import org.pixeldroid.app.utils.api.objects.Account
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@ -14,7 +14,6 @@ import javax.inject.Inject
class FollowersContentRepository @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String,
private val following: Boolean,
): UncachedContentRepository<Account> {
@ -25,7 +24,7 @@ class FollowersContentRepository @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false),
pagingSourceFactory = {
FollowersPagingSource(api, accessToken, accountId, following)
FollowersPagingSource(api, accountId, following)
}
).flow
}

View File

@ -1,16 +1,14 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists
package org.pixeldroid.app.posts.feeds.uncachedFeeds.accountLists
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Account
import retrofit2.HttpException
import java.io.IOException
import java.math.BigInteger
class FollowersPagingSource(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String,
private val following: Boolean
) : PagingSource<String, Account>() {
@ -22,17 +20,19 @@ class FollowersPagingSource(
// Laravel's paging mechanism, while Mastodon uses the Link header for pagination.
// No need to know which is which, they should ignore the non-relevant argument
if(following) {
api.followers(account_id = accountId,
authorization = "Bearer $accessToken",
api.followers(
account_id = accountId,
max_id = position,
limit = params.loadSize,
page = position,
max_id = position)
page = position
)
} else {
api.following(account_id = accountId,
authorization = "Bearer $accessToken",
api.following(
account_id = accountId,
max_id = position,
limit = params.loadSize,
page = position,
max_id = position)
page = position
)
}
val accounts = if(response.isSuccessful){
@ -41,25 +41,22 @@ class FollowersPagingSource(
throw HttpException(response)
}
val nextPosition: String = if(response.headers()["Link"] != null){
//Header is of the form:
val nextPosition: String = response.headers()["Link"]
// Header is of the form:
// Link: <https://mastodon.social/api/v1/accounts/1/followers?limit=2&max_id=7628164>; rel="next", <https://mastodon.social/api/v1/accounts/1/followers?limit=2&since_id=7628165>; rel="prev"
// So we want the first max_id value. In case there are arguments after
// the max_id in the URL, we make sure to stop at the first '?'
response.headers()["Link"]
.orEmpty()
.substringAfter("max_id=", "")
.substringBefore('?', "")
.substringBefore('>', "")
} else {
// No Link header, so we just increment the position value
?.substringAfter("max_id=", "")
?.substringBefore('?', "")
?.substringBefore('>', "")
?: // No Link header, so we just increment the position value (Pixelfed case)
(position?.toBigIntegerOrNull() ?: 1.toBigInteger()).inc().toString()
}
LoadResult.Page(
data = accounts,
prevKey = null,
nextKey = if (accounts.isEmpty()) null else nextPosition
nextKey = if (accounts.isEmpty() || nextPosition.isEmpty() || nextPosition == position) null else nextPosition
)
} catch (exception: IOException) {
LoadResult.Error(exception)
@ -68,8 +65,5 @@ class FollowersPagingSource(
}
}
override fun getRefreshKey(state: PagingState<String, Account>): String? =
state.anchorPosition?.run {
state.closestItemToPosition(this)?.id
}
override fun getRefreshKey(state: PagingState<String, Account>): String? = null
}

View File

@ -0,0 +1,35 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds.hashtags
import android.os.Bundle
import org.pixeldroid.app.R
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedPostsFragment
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.api.objects.Tag.Companion.HASHTAG_TAG
class HashTagActivity : BaseActivity() {
private var tagFragment = UncachedPostsFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_followers)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
// Get hashtag tag
val tag = intent.getSerializableExtra(HASHTAG_TAG) as String?
startFragment(tag!!)
}
private fun startFragment(tag : String) {
supportActionBar?.title = getString(R.string.hashtag_title).format(tag)
val arguments = Bundle()
arguments.putSerializable(HASHTAG_TAG, tag)
tagFragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.followsFragment, tagFragment).commit()
}
}

View File

@ -0,0 +1,38 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds.hashtags
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import org.pixeldroid.app.utils.api.objects.FeedContent
import org.pixeldroid.app.utils.api.objects.Results
import kotlinx.coroutines.flow.Flow
import org.pixeldroid.app.utils.api.objects.Status
import javax.inject.Inject
/**
* Repository class for viewing hashtags
*/
class HashTagContentRepository @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val hashtag: String,
): UncachedContentRepository<Status> {
override fun getStream(): Flow<PagingData<Status>> {
return Pager(
config = PagingConfig(
initialLoadSize = NETWORK_PAGE_SIZE,
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false),
pagingSourceFactory = {
HashTagPagingSource(api, hashtag)
}
).flow
}
companion object {
private const val NETWORK_PAGE_SIZE = 20
}
}

View File

@ -0,0 +1,47 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds.hashtags
import androidx.paging.PagingSource
import androidx.paging.PagingState
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.FeedContent
import org.pixeldroid.app.utils.api.objects.Results
import org.pixeldroid.app.utils.api.objects.Status
import retrofit2.HttpException
import java.io.IOException
/**
* Provides the PagingSource for hashtag feeds. Is used in [HashTagContentRepository]
*/
class HashTagPagingSource(
private val api: PixelfedAPI,
private val query: String,
) : PagingSource<String, Status>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
val position = params.key
return try {
val response = api.hashtag(
hashtag = query,
limit = params.loadSize,
max_id = position,
)
val nextKey = response.lastOrNull()?.id
LoadResult.Page(
data = response,
prevKey = null,
nextKey = if(nextKey == position) null else nextKey
)
} catch (exception: HttpException) {
LoadResult.Error(exception)
} catch (exception: IOException) {
LoadResult.Error(exception)
}
}
/**
* FIXME if implemented with [PagingState.anchorPosition], this breaks refreshes? How is this
* supposed to work?
*/
override fun getRefreshKey(state: PagingState<String, Status>): String? = null
}

View File

@ -1,19 +1,18 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.profile
package org.pixeldroid.app.posts.feeds.uncachedFeeds.profile
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Status
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Status
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class ProfileContentRepository @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String
) : UncachedContentRepository<Status> {
override fun getStream(): Flow<PagingData<Status>> {
@ -23,7 +22,7 @@ class ProfileContentRepository @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false),
pagingSourceFactory = {
ProfilePagingSource(api, accessToken, accountId)
ProfilePagingSource(api, accountId)
}
).flow
}

View File

@ -1,30 +1,31 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.profile
package org.pixeldroid.app.posts.feeds.uncachedFeeds.profile
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Status
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Status
import retrofit2.HttpException
import java.io.IOException
class ProfilePagingSource(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String
) : PagingSource<String, Status>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
val position = params.key
return try {
val posts = api.accountPosts("Bearer $accessToken",
account_id = accountId,
max_id = position,
limit = params.loadSize
val posts = api.accountPosts(
account_id = accountId,
max_id = position,
limit = params.loadSize
)
val nextKey = posts.lastOrNull()?.id
LoadResult.Page(
data = posts,
prevKey = null,
nextKey = posts.lastOrNull()?.id
nextKey = if(nextKey == position) null else nextKey
)
} catch (exception: HttpException) {
LoadResult.Error(exception)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.search
package org.pixeldroid.app.posts.feeds.uncachedFeeds.search
import android.os.Bundle
import android.view.LayoutInflater
@ -6,10 +6,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.paging.ExperimentalPagingApi
import com.h.pixeldroid.posts.feeds.uncachedFeeds.*
import com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists.AccountAdapter
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Results
import org.pixeldroid.app.posts.feeds.uncachedFeeds.*
import org.pixeldroid.app.posts.feeds.uncachedFeeds.accountLists.AccountAdapter
import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.api.objects.Results
/**
* Fragment to show a list of [Account]s, as a result of a search.
@ -37,15 +37,14 @@ class SearchAccountFragment : UncachedFeedFragment<Account>() {
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory(
viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
SearchContentRepository<Account>(
apiHolder.setDomainToCurrentUser(db),
apiHolder.setToCurrentUser(),
Results.SearchType.accounts,
db.userDao().getActiveUser()!!.accessToken,
query
)
)
).get(FeedViewModel::class.java) as FeedViewModel<Account>
).get("searchAccounts", FeedViewModel::class.java) as FeedViewModel<Account>
launch()
initSearch()

View File

@ -1,13 +1,13 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.search
package org.pixeldroid.app.posts.feeds.uncachedFeeds.search
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.utils.api.objects.FeedContent
import com.h.pixeldroid.utils.api.objects.Results
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import org.pixeldroid.app.utils.api.objects.FeedContent
import org.pixeldroid.app.utils.api.objects.Results
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@ -15,14 +15,13 @@ import javax.inject.Inject
* Repository class to perform searches
*
* The type argument [T] and the [Results.SearchType][type] argument should always
* be in agreement, e.g. if [T] is a [com.h.pixeldroid.utils.api.objects.Account] then
* be in agreement, e.g. if [T] is a [org.pixeldroid.app.utils.api.objects.Account] then
* [type] should be [Results.SearchType.accounts].
*/
class SearchContentRepository<T: FeedContent> @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val type: Results.SearchType,
private val accessToken: String,
private val query: String,
): UncachedContentRepository<T> {
override fun getStream(): Flow<PagingData<T>> {
@ -32,7 +31,7 @@ class SearchContentRepository<T: FeedContent> @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false),
pagingSourceFactory = {
SearchPagingSource<T>(api, query, type, accessToken)
SearchPagingSource<T>(api, query, type)
}
).flow
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.search
package org.pixeldroid.app.posts.feeds.uncachedFeeds.search
import android.annotation.SuppressLint
import android.os.Bundle
@ -11,13 +11,14 @@ import androidx.paging.ExperimentalPagingApi
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.FragmentTagsBinding
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.ViewModelFactory
import com.h.pixeldroid.utils.api.objects.Results
import com.h.pixeldroid.utils.api.objects.Tag
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.FragmentTagsBinding
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedFeedFragment
import org.pixeldroid.app.posts.feeds.uncachedFeeds.ViewModelFactory
import org.pixeldroid.app.utils.api.objects.Results
import org.pixeldroid.app.utils.api.objects.Tag
import org.pixeldroid.app.utils.api.objects.Tag.Companion.openTag
/**
* Fragment to show a list of [hashtag][Tag]s, as a result of a search.
@ -44,16 +45,15 @@ class SearchHashtagFragment : UncachedFeedFragment<Tag>() {
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory(
viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
SearchContentRepository<Tag>(
apiHolder.setDomainToCurrentUser(db),
apiHolder.setToCurrentUser(),
Results.SearchType.hashtags,
db.userDao().getActiveUser()!!.accessToken,
query
)
)
)
.get(FeedViewModel::class.java) as FeedViewModel<Tag>
.get("searchHashtag", FeedViewModel::class.java) as FeedViewModel<Tag>
launch()
initSearch()
@ -87,7 +87,7 @@ class HashTagAdapter : PagingDataAdapter<Tag, RecyclerView.ViewHolder>(
companion object {
private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback<Tag>() {
override fun areItemsTheSame(oldItem: Tag, newItem: Tag): Boolean {
return oldItem.id == newItem.id
return oldItem.name == newItem.name
}
override fun areContentsTheSame(oldItem: Tag, newItem: Tag): Boolean =
@ -108,7 +108,9 @@ class HashTagViewHolder(binding: FragmentTagsBinding) : RecyclerView.ViewHolder(
init {
itemView.setOnClickListener {
//TODO
tag?.apply {
openTag(itemView.context, this.name)
}
}
}

View File

@ -1,10 +1,10 @@
package com.h.pixeldroid.posts.feeds.uncachedFeeds.search
package org.pixeldroid.app.posts.feeds.uncachedFeeds.search
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.FeedContent
import com.h.pixeldroid.utils.api.objects.Results
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.FeedContent
import org.pixeldroid.app.utils.api.objects.Results
import retrofit2.HttpException
import java.io.IOException
@ -15,16 +15,16 @@ class SearchPagingSource<T: FeedContent>(
private val api: PixelfedAPI,
private val query: String,
private val type: Results.SearchType,
private val accessToken: String,
) : PagingSource<Int, T>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val position = params.key
return try {
val response = api.search(authorization = "Bearer $accessToken",
offset = position?.toString(),
q = query,
val response = api.search(
type = type,
limit = params.loadSize.toString())
q = query,
limit = params.loadSize.toString(),
offset = position?.toString()
)
@Suppress("UNCHECKED_CAST")
@ -34,10 +34,12 @@ class SearchPagingSource<T: FeedContent>(
Results.SearchType.statuses -> response.statuses
} as List<T>
val nextKey = if (repos.isEmpty()) null else (position ?: 0) + repos.size
LoadResult.Page(
data = repos,
prevKey = null,
nextKey = if (repos.isEmpty()) null else (position ?: 0) + repos.size
nextKey = if(nextKey == position) null else nextKey
)
} catch (exception: HttpException) {
LoadResult.Error(exception)
@ -46,8 +48,9 @@ class SearchPagingSource<T: FeedContent>(
}
}
override fun getRefreshKey(state: PagingState<Int, T>): Int? =
state.anchorPosition?.run {
state.closestItemToPosition(this)?.id?.toIntOrNull()
}
/**
* FIXME if implemented with [PagingState.anchorPosition], this breaks refreshes? How is this
* supposed to work?
*/
override fun getRefreshKey(state: PagingState<Int, T>): Int? = null
}

View File

@ -1,13 +1,13 @@
package com.h.pixeldroid.profile
package org.pixeldroid.app.profile
import android.os.Bundle
import com.h.pixeldroid.R
import com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists.AccountListFragment
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_TAG
import com.h.pixeldroid.utils.api.objects.Account.Companion.FOLLOWERS_TAG
import com.h.pixeldroid.utils.BaseActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.posts.feeds.uncachedFeeds.accountLists.AccountListFragment
import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG
import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_TAG
import org.pixeldroid.app.utils.api.objects.Account.Companion.FOLLOWERS_TAG
import org.pixeldroid.app.utils.BaseActivity
class FollowsActivity : BaseActivity() {
@ -31,11 +31,6 @@ class FollowsActivity : BaseActivity() {
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun startFragment(id : String, displayName: String, followers : Boolean) {
supportActionBar?.title =
if (followers) {

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.profile
package org.pixeldroid.app.profile
import android.content.Intent
import android.os.Bundle
@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
@ -18,34 +19,31 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ActivityProfileBinding
import com.h.pixeldroid.databinding.FragmentProfilePostsBinding
import com.h.pixeldroid.posts.PostActivity
import com.h.pixeldroid.posts.feeds.initAdapter
import com.h.pixeldroid.posts.feeds.launch
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.posts.feeds.uncachedFeeds.profile.ProfileContentRepository
import com.h.pixeldroid.posts.parseHTMLText
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.ImageConverter
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.FeedContent
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.utils.openUrl
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityProfileBinding
import org.pixeldroid.app.databinding.FragmentProfilePostsBinding
import org.pixeldroid.app.posts.PostActivity
import org.pixeldroid.app.posts.feeds.initAdapter
import org.pixeldroid.app.posts.feeds.launch
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import org.pixeldroid.app.posts.feeds.uncachedFeeds.profile.ProfileContentRepository
import org.pixeldroid.app.posts.parseHTMLText
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.ImageConverter
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.api.objects.FeedContent
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.openUrl
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class ProfileActivity : BaseActivity() {
private lateinit var pixelfedAPI : PixelfedAPI
private lateinit var accessToken : String
private lateinit var domain : String
private lateinit var accountId : String
private lateinit var binding: ActivityProfileBinding
@ -66,8 +64,6 @@ class ProfileActivity : BaseActivity() {
user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty()
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
accessToken = user?.accessToken.orEmpty()
// Set profile according to given account
val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account?
@ -77,9 +73,8 @@ class ProfileActivity : BaseActivity() {
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ProfileViewModelFactory(
ProfileContentRepository(
apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
accountId
apiHolder.setToCurrentUser(),
accountId
)
)
).get(FeedViewModel::class.java) as FeedViewModel<Status>
@ -96,9 +91,7 @@ class ProfileActivity : BaseActivity() {
}
setContent(account)
@Suppress("UNCHECKED_CAST")
job = launch(job, lifecycleScope, viewModel as FeedViewModel<FeedContent>,
profileAdapter as PagingDataAdapter<FeedContent, RecyclerView.ViewHolder>)
job = launch(job, lifecycleScope, viewModel, profileAdapter)
}
/**
@ -114,18 +107,14 @@ class ProfileActivity : BaseActivity() {
binding.profileRefreshLayout.isRefreshing = false
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun setContent(account: Account?) {
if(account != null) {
setViews(account)
} else {
lifecycleScope.launchWhenResumed {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
val myAccount: Account = try {
pixelfedAPI.verifyCredentials("Bearer $accessToken")
api.verifyCredentials()
} catch (exception: IOException) {
Log.e("ProfileActivity:", exception.toString())
return@launchWhenResumed showError()
@ -162,9 +151,9 @@ class ProfileActivity : BaseActivity() {
)
binding.descriptionTextView.text = parseHTMLText(
account.note ?: "", emptyList(), pixelfedAPI,
applicationContext, "Bearer $accessToken",
lifecycleScope
account.note ?: "", emptyList(), apiHolder,
applicationContext,
lifecycleScope
)
val displayName = account.getusername()
@ -235,13 +224,14 @@ class ProfileActivity : BaseActivity() {
// Get relationship between the two users (credential and this) and set followButton accordingly
lifecycleScope.launch {
try {
val relationship = pixelfedAPI.checkRelationships(
"Bearer $accessToken", listOf(account.id.orEmpty())
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
val relationship = api.checkRelationships(
listOf(account.id.orEmpty())
).firstOrNull()
if(relationship != null){
if (relationship.following) {
setOnClickUnfollow(account)
if (relationship.following == true || relationship.requested == true) {
setOnClickUnfollow(account, relationship.requested == true)
} else {
setOnClickFollow(account)
}
@ -268,8 +258,10 @@ class ProfileActivity : BaseActivity() {
setOnClickListener {
lifecycleScope.launchWhenResumed {
try {
pixelfedAPI.follow(account.id.orEmpty(), "Bearer $accessToken")
setOnClickUnfollow(account)
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
val rel = api.follow(account.id.orEmpty())
if(rel.following == true) setOnClickUnfollow(account, rel.requested == true)
else setOnClickFollow(account)
} catch (exception: IOException) {
Log.e("FOLLOW ERROR", exception.toString())
Toast.makeText(
@ -287,29 +279,46 @@ class ProfileActivity : BaseActivity() {
}
}
private fun setOnClickUnfollow(account: Account) {
private fun setOnClickUnfollow(account: Account, requested: Boolean) {
binding.followButton.apply {
setText(R.string.unfollow)
if(account.locked == true && requested) {
setText(R.string.follow_requested)
} else setText(R.string.unfollow)
setOnClickListener {
fun unfollow() {
lifecycleScope.launchWhenResumed {
try {
pixelfedAPI.unfollow(account.id.orEmpty(), "Bearer $accessToken")
setOnClickFollow(account)
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
val rel = api.unfollow(account.id.orEmpty())
if(rel.following == false && rel.requested == false) setOnClickFollow(account)
else setOnClickUnfollow(account, rel.requested == true)
} catch (exception: IOException) {
Log.e("FOLLOW ERROR", exception.toString())
Toast.makeText(
applicationContext, getString(R.string.unfollow_error),
Toast.LENGTH_SHORT
applicationContext, getString(R.string.unfollow_error),
Toast.LENGTH_SHORT
).show()
} catch (exception: HttpException) {
Toast.makeText(
applicationContext, getString(R.string.unfollow_error),
Toast.LENGTH_SHORT
applicationContext, getString(R.string.unfollow_error),
Toast.LENGTH_SHORT
).show()
}
}
}
setOnClickListener {
if(account.locked == true && requested){
AlertDialog.Builder(context)
.setMessage(R.string.dialog_message_cancel_follow_request)
.setPositiveButton(android.R.string.ok) { _, _ ->
unfollow()
}
.setNegativeButton(android.R.string.cancel){_, _ -> }
.show()
} else unfollow()
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.profile
package org.pixeldroid.app.profile
import android.content.Intent
import android.view.LayoutInflater
@ -7,11 +7,11 @@ import android.view.ViewGroup
import android.widget.ImageView
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.posts.PostActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.utils.ImageConverter.Companion.setSquareImageFromDrawable
import com.h.pixeldroid.utils.ImageConverter.Companion.setSquareImageFromURL
import org.pixeldroid.app.posts.PostActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.ImageConverter.Companion.setSquareImageFromDrawable
import org.pixeldroid.app.utils.ImageConverter.Companion.setSquareImageFromURL
/**
* [RecyclerView.Adapter] that can display a list of [Status]s

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.searchDiscover
package org.pixeldroid.app.searchDiscover
import android.app.SearchManager
import android.content.Intent
@ -8,12 +8,12 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.h.pixeldroid.R
import com.h.pixeldroid.posts.feeds.uncachedFeeds.search.SearchAccountFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.search.SearchHashtagFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.search.SearchPostsFragment
import com.h.pixeldroid.utils.api.objects.Results
import com.h.pixeldroid.utils.BaseActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedPostsFragment
import org.pixeldroid.app.posts.feeds.uncachedFeeds.search.SearchAccountFragment
import org.pixeldroid.app.posts.feeds.uncachedFeeds.search.SearchHashtagFragment
import org.pixeldroid.app.utils.api.objects.Results
import org.pixeldroid.app.utils.BaseActivity
class SearchActivity : BaseActivity() {
@ -47,14 +47,9 @@ class SearchActivity : BaseActivity() {
setupTabs(tabs, searchType)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun createSearchTabs(query: String): Array<Fragment>{
val searchFeedFragment = SearchPostsFragment()
val searchFeedFragment = UncachedPostsFragment()
val searchAccountListFragment =
SearchAccountFragment()
val searchHashtagFragment: Fragment = SearchHashtagFragment()

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.searchDiscover
package org.pixeldroid.app.searchDiscover
import android.app.SearchManager
import android.content.Context
@ -11,21 +11,15 @@ import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.FragmentSearchBinding
import com.h.pixeldroid.profile.ProfilePostViewHolder
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Status
import com.h.pixeldroid.posts.PostActivity
import com.h.pixeldroid.utils.BaseFragment
import com.h.pixeldroid.utils.ImageConverter
import com.h.pixeldroid.utils.bindingLifecycleAware
import com.mikepenz.iconics.IconicsColor
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.color
import com.mikepenz.iconics.utils.paddingDp
import com.mikepenz.iconics.utils.sizeDp
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.FragmentSearchBinding
import org.pixeldroid.app.profile.ProfilePostViewHolder
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.posts.PostActivity
import org.pixeldroid.app.utils.BaseFragment
import org.pixeldroid.app.utils.ImageConverter
import org.pixeldroid.app.utils.bindingLifecycleAware
import retrofit2.HttpException
import java.io.IOException
@ -37,7 +31,6 @@ class SearchDiscoverFragment : BaseFragment() {
private lateinit var api: PixelfedAPI
private lateinit var recycler : RecyclerView
private lateinit var adapter : DiscoverRecyclerViewAdapter
private lateinit var accessToken: String
var binding: FragmentSearchBinding by bindingLifecycleAware()
@ -66,9 +59,7 @@ class SearchDiscoverFragment : BaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
accessToken = db.userDao().getActiveUser()?.accessToken.orEmpty()
api = apiHolder.api ?: apiHolder.setToCurrentUser()
getDiscover()
@ -93,8 +84,9 @@ class SearchDiscoverFragment : BaseFragment() {
private fun getDiscover() {
lifecycleScope.launchWhenCreated {
try {
val discoverPosts = api.discover("Bearer $accessToken")
val discoverPosts = api.discover()
adapter.addPosts(discoverPosts.posts)
binding.discoverNoInfiniteLoad.visibility = View.VISIBLE
showError(show = false)
} catch (exception: IOException) {
showError()
@ -108,7 +100,7 @@ class SearchDiscoverFragment : BaseFragment() {
* [RecyclerView.Adapter] that can display a list of [Status]s' thumbnails for the discover view
*/
class DiscoverRecyclerViewAdapter: RecyclerView.Adapter<ProfilePostViewHolder>() {
private val posts: ArrayList<Status> = ArrayList()
private val posts: ArrayList<Status?> = ArrayList()
fun addPosts(newPosts : List<Status>) {
posts.clear()
@ -124,12 +116,12 @@ class SearchDiscoverFragment : BaseFragment() {
override fun onBindViewHolder(holder: ProfilePostViewHolder, position: Int) {
val post = posts[position]
if(post.media_attachments?.size ?: 0 > 1) {
if(post?.media_attachments?.size ?: 0 > 1) {
holder.albumIcon.visibility = View.VISIBLE
} else {
holder.albumIcon.visibility = View.GONE
}
ImageConverter.setSquareImageFromURL(holder.postView, post.media_attachments?.firstOrNull()?.preview_url, holder.postPreview, post.media_attachments?.firstOrNull()?.blurhash)
ImageConverter.setSquareImageFromURL(holder.postView, post?.media_attachments?.firstOrNull()?.preview_url, holder.postPreview, post?.media_attachments?.firstOrNull()?.blurhash)
holder.postPreview.setOnClickListener {
val intent = Intent(holder.postView.context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)

View File

@ -1,11 +1,11 @@
package com.h.pixeldroid.settings
package org.pixeldroid.app.settings
import android.content.Intent
import android.os.Bundle
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ActivityAboutBinding
import com.h.pixeldroid.utils.BaseActivity
import org.pixeldroid.app.BuildConfig
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityAboutBinding
import org.pixeldroid.app.utils.BaseActivity
class AboutActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -1,9 +1,9 @@
package com.h.pixeldroid.settings
package org.pixeldroid.app.settings
import android.os.Bundle
import com.h.pixeldroid.R
import com.h.pixeldroid.databinding.ActivityLicensesBinding
import com.h.pixeldroid.utils.BaseActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityLicensesBinding
import org.pixeldroid.app.utils.BaseActivity
class LicenseActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -1,14 +1,14 @@
package com.h.pixeldroid.settings
package org.pixeldroid.app.settings
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.h.pixeldroid.MainActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.setThemeFromPreferences
import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.setThemeFromPreferences
class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
private var restartMainOnExit = false

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.utils
package org.pixeldroid.app.utils
import android.content.Context
import android.content.res.Configuration
@ -7,8 +7,8 @@ import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import java.util.*
import javax.inject.Inject
@ -28,6 +28,11 @@ open class BaseActivity : AppCompatActivity() {
super.attachBaseContext(updateBaseContextLocale(base))
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun updateBaseContextLocale(context: Context): Context {
val language = PreferenceManager.getDefaultSharedPreferences(context).getString("language", "default") ?: "default"
if(language == "default"){

View File

@ -1,9 +1,9 @@
package com.h.pixeldroid.utils
package org.pixeldroid.app.utils
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import javax.inject.Inject
/**

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.utils
package org.pixeldroid.app.utils
/**
* Blurhash implementation from blurhash project:
@ -11,7 +11,6 @@ import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import com.h.pixeldroid.utils.api.objects.Attachment
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.pow

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.utils
package org.pixeldroid.app.utils
import android.graphics.drawable.Drawable
import android.view.View
@ -8,7 +8,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.h.pixeldroid.R
import org.pixeldroid.app.R
class ImageConverter {
companion object {

View File

@ -1,11 +1,10 @@
package com.h.pixeldroid.utils
package org.pixeldroid.app.utils
import android.app.Application
import androidx.preference.PreferenceManager
import com.h.pixeldroid.utils.di.*
import org.pixeldroid.app.utils.di.*
import com.mikepenz.iconics.Iconics
import org.ligi.tracedroid.TraceDroid
import org.ligi.tracedroid.sending.sendTraceDroidStackTracesIfExist
class PixelDroidApplication: Application() {
@ -27,8 +26,6 @@ class PixelDroidApplication: Application() {
.aPIModule(APIModule())
.build()
mApplicationComponent.inject(this)
Iconics.init(applicationContext)
}
fun getAppComponent(): ApplicationComponent {

Some files were not shown because too many files have changed in this diff Show More