Merge branch 'master' into click_notifications

This commit is contained in:
mjaillot 2021-09-06 16:08:48 +02:00
commit 321d7ca7ca
195 changed files with 3103 additions and 1464 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 .cxx
.idea .idea
app/release app/release
app/debug
app/lint app/lint
lint lint

View File

@ -7,6 +7,14 @@ variables:
TARGET: "default" TARGET: "default"
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
# Basic android and gradle stuff # Basic android and gradle stuff
# Check linting # Check linting
lintDebug: lintDebug:
@ -52,6 +60,62 @@ emulatorTest:
- cat app/build/reports/jacoco/jacocoTestReport/html/index.html | grep -o 'Total[^%]*%' - cat app/build/reports/jacoco/jacocoTestReport/html/index.html | grep -o 'Total[^%]*%'
artifacts: artifacts:
when: always
paths: paths:
- ./app/build/reports/jacoco/jacocoTestReport/ - ./app/build/reports/jacoco/jacocoTestReport/
expire_in: 1 week 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
![Pixeldroid project logo](pixeldroid_logo.png) ![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) [![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 ## 🔧 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/). 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: 'kotlin-kapt'
apply plugin: 'jacoco' 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 { android {
@ -21,11 +24,11 @@ android {
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"] freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
} }
defaultConfig { defaultConfig {
applicationId "com.h.pixeldroid" applicationId "org.pixeldroid.app"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 30 targetSdkVersion 30
versionCode 10 versionCode 3
versionName "1.0.alpha9" versionName "1.0.beta3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
@ -36,15 +39,15 @@ android {
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/java' main.java.srcDirs += 'src/main/java'
test.java.srcDirs += 'src/test/java' test.java.srcDirs += 'src/test/java'
staging.res.srcDirs += 'src/debug/res'
androidTest.java.srcDirs += 'src/androidTest/java' androidTest.java.srcDirs += 'src/androidTest/java'
} }
testBuildType "staging" testBuildType "staging"
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix '.debug'
versionNameSuffix "-debug"
} }
staging { staging {
initWith debug initWith debug
@ -61,12 +64,12 @@ android {
localProperties.load(new FileInputStream(rootProject.file("local.properties"))) localProperties.load(new FileInputStream(rootProject.file("local.properties")))
} }
buildConfigField "String", "USER_ID", System.getenv("USER_ID") ?: localProperties['USER_ID'] buildConfigField "String", "USER_ID", System.getenv("USER_ID") ?: localProperties['USER_ID'] ?: ""
buildConfigField "String", "INSTANCE_URI", System.getenv("INSTANCE_URI") ?: localProperties['INSTANCE_URI'] buildConfigField "String", "INSTANCE_URI", System.getenv("INSTANCE_URI") ?: localProperties['INSTANCE_URI'] ?: ""
buildConfigField "String", "ACCESS_TOKEN", System.getenv("ACCESS_TOKEN") ?: localProperties['ACCESS_TOKEN'] buildConfigField "String", "ACCESS_TOKEN", System.getenv("ACCESS_TOKEN") ?: localProperties['ACCESS_TOKEN'] ?: ""
buildConfigField "String", "REFRESH_TOKEN", System.getenv("REFRESH_TOKEN") ?: localProperties['REFRESH_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_ID", System.getenv("CLIENT_ID") ?: localProperties['CLIENT_ID'] ?: ""
buildConfigField "String", "CLIENT_SECRET", System.getenv("CLIENT_SECRET") ?: localProperties['CLIENT_SECRET'] buildConfigField "String", "CLIENT_SECRET", System.getenv("CLIENT_SECRET") ?: localProperties['CLIENT_SECRET'] ?: ""
} }
release { release {
minifyEnabled true minifyEnabled true
@ -74,6 +77,14 @@ android {
proguardFiles 'proguard-rules.pro' 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 { testOptions {
animationsDisabled true animationsDisabled true
@ -92,37 +103,38 @@ dependencies {
/** /**
* AndroidX dependencies: * AndroidX dependencies:
*/ */
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation "androidx.browser:browser:1.3.0" 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.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-beta02' implementation 'androidx.paging:paging-runtime-ktx:3.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.2.0"
implementation 'androidx.gridlayout:gridlayout:1.0.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 // 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-core:${cameraX_version}"
implementation "androidx.camera:camera-camera2:${cameraX_version}" implementation "androidx.camera:camera-camera2:${cameraX_version}"
// CameraX Lifecycle library // CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:$cameraX_version" implementation "androidx.camera:camera-lifecycle:$cameraX_version"
// CameraX View class // 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" implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
@ -136,17 +148,17 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
//Dagger (dependency injection) //Dagger (dependency injection)
implementation 'com.google.dagger:dagger-android:2.30.1' implementation 'com.google.dagger:dagger-android:2.37'
implementation 'com.google.dagger:dagger-android-support:2.30.1' implementation 'com.google.dagger:dagger-android-support:2.37'
// if you use the support libraries // if you use the support libraries
kapt 'com.google.dagger:dagger-android-processor:2.30.1' kapt 'com.google.dagger:dagger-android-processor:2.37'
kapt 'com.google.dagger:dagger-compiler:2.30.1' 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:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2: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 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.github.connyduck:sparkbutton:4.1.0' implementation 'com.github.connyduck:sparkbutton:4.1.0'
@ -154,26 +166,26 @@ dependencies {
implementation 'info.androidhive:imagefilters:1.0.7' implementation 'info.androidhive:imagefilters:1.0.7'
implementation 'com.github.yalantis:ucrop:2.2.6-native' 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" exclude group: "com.android.support"
} }
implementation "com.github.bumptech.glide:okhttp-integration:4.11.0" implementation 'com.github.bumptech.glide:okhttp-integration:4.12.0'
implementation("com.github.bumptech.glide:recyclerview-integration:4.11.0") { implementation('com.github.bumptech.glide:recyclerview-integration:4.12.0') {
// Excludes the support library because it's already included by Glide. // Excludes the support library because it's already included by Glide.
transitive = false 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 '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 // Add for NavController support
implementation "com.mikepenz:materialdrawer-nav:8.1.5" implementation 'com.mikepenz:materialdrawer-nav:8.4.0'
//iconics //iconics
implementation "com.mikepenz:iconics-core:5.0.3" implementation "com.mikepenz:iconics-core:5.2.8"
implementation "com.mikepenz:materialdrawer-iconics:8.1.8" implementation 'com.mikepenz:materialdrawer-iconics:8.4.1'
implementation "com.mikepenz:iconics-views:5.0.3" implementation "com.mikepenz:iconics-views:5.0.3"
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar' 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 // debugImplementation required vs testImplementation: https://issuetracker.google.com/issues/128612536
//noinspection FragmentGradleConfiguration //noinspection FragmentGradleConfiguration
stagingImplementation("androidx.fragment:fragment-testing:1.3.1") { stagingImplementation("androidx.fragment:fragment-testing:1.3.5") {
exclude group:'androidx.test', module:'monitor' 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.github.tomakehurst:wiremock-jre8:2.27.2'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" 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" 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 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" def mainSrc = "$project.projectDir/src/main/java"
getSourceDirectories().from(files([mainSrc])) getSourceDirectories().from(files([mainSrc]))
getClassDirectories().from(files([kotlinDebugTree])) getClassDirectories().from(files([kotlinTree]))
getExecutionData().from(fileTree(dir: project.buildDir, includes: [ getExecutionData().from(fileTree(dir: project.buildDir, includes: [
'outputs/code_coverage/stagingAndroidTest/connected/*coverage.ec', 'outputs/code_coverage/stagingAndroidTest/connected/*coverage.ec',

View File

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

View File

@ -37,9 +37,9 @@
# APP SPECIFIC OPTIONS # APP SPECIFIC OPTIONS
# keep members of our model classes, they are used in json de/serialization # 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; **[] $VALUES;
public *; public *;
} }

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid package org.pixeldroid.app
import android.Manifest import android.Manifest
import android.content.Context 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.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.postCreation.camera.CameraFragment import org.pixeldroid.app.postCreation.camera.CameraFragment
import com.h.pixeldroid.testUtility.clearData import org.pixeldroid.app.testUtility.clearData
import com.h.pixeldroid.testUtility.initDB import org.pixeldroid.app.testUtility.initDB
import org.hamcrest.CoreMatchers import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.junit.After import org.junit.After

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid package org.pixeldroid.app
import android.content.Context import android.content.Context
import androidx.test.core.app.ActivityScenario 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.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import com.h.pixeldroid.testUtility.* import org.hamcrest.Matchers.allOf
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.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -107,8 +108,10 @@ class DrawerMenuTest {
// Start the screen of your activity. // Start the screen of your activity.
onView(withText(R.string.menu_account)).perform(click()) onView(withText(R.string.menu_account)).perform(click())
// Check that profile activity was opened. // Check that profile activity was opened.
waitForView(R.id.editButton)
onView(withId(R.id.editButton)).check(matches(isDisplayed())) onView(withId(R.id.editButton)).check(matches(isDisplayed()))
val followersText = context.resources.getQuantityString(R.plurals.nb_followers, 2, 2) 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()) onView(withText(followersText)).perform(click())
waitForView(R.id.account_entry_avatar) waitForView(R.id.account_entry_avatar)
@ -120,8 +123,10 @@ class DrawerMenuTest {
// Start the screen of your activity. // Start the screen of your activity.
onView(withText(R.string.menu_account)).perform(click()) onView(withText(R.string.menu_account)).perform(click())
// Check that profile activity was opened. // Check that profile activity was opened.
waitForView(R.id.editButton)
onView(withId(R.id.editButton)).check(matches(isDisplayed())) onView(withId(R.id.editButton)).check(matches(isDisplayed()))
val followingText = context.resources.getQuantityString(R.plurals.nb_following, 3, 3) 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()) onView(withText(followingText)).perform(click())
waitForView(R.id.account_entry_avatar) 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.Context
import android.content.Intent import android.content.Intent
@ -22,13 +22,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
import com.h.pixeldroid.postCreation.photoEdit.ThumbnailAdapter import org.pixeldroid.app.postCreation.photoEdit.ThumbnailAdapter
import com.h.pixeldroid.settings.AboutActivity import org.pixeldroid.app.settings.AboutActivity
import com.h.pixeldroid.testUtility.clearData import org.pixeldroid.app.testUtility.clearData
import com.h.pixeldroid.testUtility.clickChildViewWithId import org.pixeldroid.app.testUtility.clickChildViewWithId
import com.h.pixeldroid.testUtility.slowSwipeLeft import org.pixeldroid.app.testUtility.slowSwipeLeft
import com.h.pixeldroid.testUtility.waitForView import org.pixeldroid.app.testUtility.waitForView
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.junit.* import org.junit.*
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@ -70,7 +70,9 @@ class EditPhotoTest {
file.writeBitmap(image) file.writeBitmap(image)
uri = file.toUri() 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} 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 android.content.Context
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView 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.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition 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.test.ext.junit.runners.AndroidJUnit4
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.utils.db.AppDatabase import org.hamcrest.CoreMatchers.*
import com.h.pixeldroid.posts.StatusViewHolder import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.testUtility.* import org.pixeldroid.app.posts.StatusViewHolder
import org.hamcrest.CoreMatchers.not import org.pixeldroid.app.testUtility.*
import org.hamcrest.core.IsInstanceOf
import org.hamcrest.core.StringContains.containsString
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TestRule
import org.junit.rules.Timeout import org.junit.rules.Timeout
import org.junit.runner.Description
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.model.Statement
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class HomeFeedTest { class HomeFeedTest {
@ -31,8 +37,9 @@ class HomeFeedTest {
private lateinit var db: AppDatabase private lateinit var db: AppDatabase
private lateinit var context: Context private lateinit var context: Context
@get:Rule @Rule @JvmField
var globalTimeout: Timeout = Timeout.seconds(100) var repeatRule: RepeatRule = RepeatRule()
@Before @Before
fun before(){ fun before(){
@ -47,6 +54,10 @@ class HomeFeedTest {
) )
db.close() db.close()
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
waitForView(R.id.username)
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(0))
} }
@After @After
fun after() { fun after() {
@ -54,6 +65,7 @@ class HomeFeedTest {
} }
@Test @Test
@RepeatTest
fun clickingTabOnAlbumShowsNextPhoto() { fun clickingTabOnAlbumShowsNextPhoto() {
//Wait for the feed to load //Wait for the feed to load
waitForView(R.id.postPager) waitForView(R.id.postPager)
@ -66,6 +78,40 @@ class HomeFeedTest {
} }
onView(first(withId(R.id.postPager))).check(matches(isDisplayed())) 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 @Test
fun clickingReblogButtonWorks() { fun clickingReblogButtonWorks() {
@ -127,6 +173,7 @@ class HomeFeedTest {
}*/ }*/
@Test @Test
@RepeatTest
fun clickingUsernameOpensProfile() { fun clickingUsernameOpensProfile() {
waitForView(R.id.username) waitForView(R.id.username)
@ -137,6 +184,7 @@ class HomeFeedTest {
} }
@Test @Test
@RepeatTest
fun clickingProfilePicOpensProfile() { fun clickingProfilePicOpensProfile() {
waitForView(R.id.profilePic) waitForView(R.id.profilePic)
@ -147,6 +195,7 @@ class HomeFeedTest {
} }
@Test @Test
@RepeatTest
fun clickingMentionOpensProfile() { fun clickingMentionOpensProfile() {
waitForView(R.id.description) waitForView(R.id.description)
@ -156,13 +205,6 @@ class HomeFeedTest {
onView(first(withId(R.id.username))).check(matches(isDisplayed())) 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 @Test
@ -217,6 +259,7 @@ class HomeFeedTest {
.check(matches(hasDescendant(withId(R.id.comment)))) .check(matches(hasDescendant(withId(R.id.comment))))
}*/ }*/
@RepeatTest
@Test @Test
fun performClickOnSensitiveWarning() { fun performClickOnSensitiveWarning() {
waitForView(R.id.username) waitForView(R.id.username)
@ -232,11 +275,10 @@ class HomeFeedTest {
} }
@Test @Test
@RepeatTest
fun performClickOnSensitiveWarningTabs() { fun performClickOnSensitiveWarningTabs() {
waitForView(R.id.username) 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(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withId(R.id.list)) 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.Context
import android.content.Intent 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.ActivityScenario
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso 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.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.contrib.DrawerActions 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.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule import androidx.test.rule.ActivityTestRule
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.posts.StatusViewHolder
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.api.objects.Account
import com.h.pixeldroid.posts.StatusViewHolder import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_TAG
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.settings.AboutActivity
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_TAG import org.pixeldroid.app.testUtility.*
import com.h.pixeldroid.settings.AboutActivity
import com.h.pixeldroid.testUtility.*
import org.hamcrest.CoreMatchers import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule

View File

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

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid package org.pixeldroid.app
import android.content.Context import android.content.Context
import android.content.Intent 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.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasErrorText import androidx.test.espresso.matcher.ViewMatchers.hasErrorText
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.testUtility.clearData
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.testUtility.initDB
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.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.Timeout import org.junit.rules.Timeout
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.pixeldroid.app.testUtility.PACKAGE_ID
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class LoginActivityOnlineTest { class LoginActivityOnlineTest {
@ -44,7 +40,7 @@ class LoginActivityOnlineTest {
fun setup() { fun setup() {
context = ApplicationProvider.getApplicationContext() context = ApplicationProvider.getApplicationContext()
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() pref.edit().clear().apply()
db = initDB(context) db = initDB(context)
db.clearAllTables() db.clearAllTables()
@ -81,7 +77,7 @@ class LoginActivityOnlineTest {
.putString("clientID", "iwndoiuqwnd") .putString("clientID", "iwndoiuqwnd")
.putString("clientSecret", "wlifowed") .putString("clientSecret", "wlifowed")
.apply() .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) val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
ActivityScenario.launch<LoginActivity>(intent) ActivityScenario.launch<LoginActivity>(intent)
onView(withId(R.id.editText)).check(matches( onView(withId(R.id.editText)).check(matches(
@ -91,7 +87,7 @@ class LoginActivityOnlineTest {
@Test @Test
fun incompleteIntentReturnInfoFailsTest() { 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) val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
ActivityScenario.launch<LoginActivity>(intent) ActivityScenario.launch<LoginActivity>(intent)
onView(withId(R.id.editText)).check(matches( onView(withId(R.id.editText)).check(matches(
@ -117,7 +113,7 @@ class LoginActivityOnlineTest {
.putString("clientID", testiTesto.clientId) .putString("clientID", testiTesto.clientId)
.putString("clientSecret", testiTesto.clientSecret) .putString("clientSecret", testiTesto.clientSecret)
.apply() .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) val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
ActivityScenario.launch<LoginActivity>(intent) ActivityScenario.launch<LoginActivity>(intent)
Thread.sleep(1000) 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
import android.content.Intent.ACTION_VIEW 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.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import org.pixeldroid.app.BuildConfig.INSTANCE_URI
import androidx.test.rule.ActivityTestRule import org.pixeldroid.app.testUtility.clearData
import androidx.test.uiautomator.UiDevice import org.pixeldroid.app.testUtility.waitForView
import com.h.pixeldroid.BuildConfig.INSTANCE_URI
import com.h.pixeldroid.testUtility.clearData
import com.h.pixeldroid.testUtility.waitForView
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.Matcher import org.hamcrest.Matcher

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid package org.pixeldroid.app
import android.content.Context import android.content.Context
@ -11,8 +11,8 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.testUtility.* import org.pixeldroid.app.testUtility.*
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid package org.pixeldroid.app
import android.Manifest import android.Manifest
import android.content.ClipData import android.content.ClipData
@ -7,26 +7,21 @@ import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.util.Log
import android.view.View.VISIBLE
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click 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.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import com.h.pixeldroid.postCreation.PostCreationActivity import org.pixeldroid.app.postCreation.PostCreationActivity
import com.h.pixeldroid.postCreation.photoEdit.ThumbnailAdapter import org.pixeldroid.app.settings.AboutActivity
import com.h.pixeldroid.settings.AboutActivity import org.pixeldroid.app.testUtility.*
import com.h.pixeldroid.testUtility.* import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.AppDatabase
import org.hamcrest.CoreMatchers.not
import org.junit.* import org.junit.*
import org.junit.rules.Timeout import org.junit.rules.Timeout
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -44,7 +39,8 @@ class PostCreationActivityTest {
val globalTimeout: Timeout = Timeout.seconds(30) val globalTimeout: Timeout = Timeout.seconds(30)
@get:Rule @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) { private fun File.writeBitmap(bitmap: Bitmap) {
outputStream().use { out -> outputStream().use { out ->
@ -112,32 +108,101 @@ class PostCreationActivityTest {
// should send on main activity // should send on main activity
onView(withId(R.id.retry_upload_button)).check(matches(not(isDisplayed()))) 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 @Test
fun editImage() { fun editImage() {
Thread.sleep(1000) waitForView(R.id.postTextInputLayout)
onView(withId(R.id.image_grid)).perform( onView(withId(R.id.editPhotoButton)).perform(click())
RecyclerViewActions.actionOnItemAtPosition<PostCreationActivity.PostCreationAdapter.ViewHolder>(
0,
CustomMatchers.clickChildViewWithId(R.id.galleryImage)
)
)
Thread.sleep(1000)
onView(withId(R.id.recycler_view)) waitForView(R.id.cropImageButton)
.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()))
} }
/**
* 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 @Test
fun cancelEdit() { fun cancelEdit() {
onView(withId(R.id.image_grid)).perform( 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.Context
import android.content.Intent import android.content.Intent
@ -17,8 +17,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.testUtility.* import org.pixeldroid.app.testUtility.*
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.junit.After import org.junit.After
import org.junit.Before 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.Context
import android.content.Intent import android.content.Intent
@ -13,16 +13,14 @@ import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.h.pixeldroid.BuildConfig.INSTANCE_URI import org.pixeldroid.app.BuildConfig.INSTANCE_URI
import com.h.pixeldroid.posts.PostActivity import org.pixeldroid.app.posts.PostActivity
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.api.objects.*
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.testUtility.clearData
import com.h.pixeldroid.utils.api.objects.* import org.pixeldroid.app.testUtility.initDB
import com.h.pixeldroid.testUtility.clearData import org.pixeldroid.app.testUtility.testiTesto
import com.h.pixeldroid.testUtility.initDB import org.pixeldroid.app.testUtility.testiTestoInstance
import com.h.pixeldroid.testUtility.testiTesto
import com.h.pixeldroid.testUtility.testiTestoInstance
import org.hamcrest.CoreMatchers.anyOf import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.junit.* import org.junit.*

View File

@ -1,29 +1,24 @@
package com.h.pixeldroid package org.pixeldroid.app
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.swipeDown import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.h.pixeldroid.postCreation.PostCreationActivity import org.pixeldroid.app.profile.ProfileActivity
import com.h.pixeldroid.profile.ProfileActivity import org.pixeldroid.app.testUtility.*
import com.h.pixeldroid.testUtility.* import org.pixeldroid.app.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.AppDatabase
import okhttp3.internal.wait
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.Serializable
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ProfileTest { 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.SpannableString
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
@ -8,6 +8,7 @@ import android.widget.TextView
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.* import androidx.test.espresso.*
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.* import androidx.test.espresso.action.*
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.BoundedMatcher 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.matcher.ViewMatchers.*
import androidx.test.espresso.util.HumanReadables import androidx.test.espresso.util.HumanReadables
import androidx.test.espresso.util.TreeIterables import androidx.test.espresso.util.TreeIterables
import com.h.pixeldroid.R
import org.hamcrest.BaseMatcher import org.hamcrest.BaseMatcher
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Description import org.hamcrest.Description
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.Matchers 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 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 { fun ViewInteraction.isDisplayed(): Boolean {
return try { return try {
check(matches(ViewMatchers.isDisplayed())) 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!) * Doesn't work if the root changes (since it operates on the root!)
* @param viewId The id of the view to wait for. * @param viewId The id of the view to wait for.
*/ */
fun waitForView(viewId: Int) { fun waitForView(viewId: Int, viewMatcher: Matcher<View> = withId(viewId)) {
Espresso.onView(isRoot()).perform(waitForViewViewAction(viewId)) Espresso.onView(isRoot()).perform(waitForViewViewAction(viewId, viewMatcher))
} }
/** /**
* This ViewAction tells espresso to wait till a certain view is found in the view hierarchy. * 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. * @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) // The maximum time which espresso will wait for the view to show up (in milliseconds)
val timeOut = 5000 val timeOut = 5000
return object : ViewAction { return object : ViewAction {
@ -62,7 +91,6 @@ private fun waitForViewViewAction(viewId: Int): ViewAction {
uiController.loopMainThreadUntilIdle() uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val endTime = startTime + timeOut val endTime = startTime + timeOut
val viewMatcher = withId(viewId)
do { do {
// Iterate through all views on the screen and see if the view we are looking for is there already // 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. // 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. // 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) 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() throw PerformException.Builder()
.withCause(TimeoutException()) .withCause(TimeoutException())
.withActionDescription(this.description) .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 { fun typeTextInViewWithId(id: Int, text: String) = object : ViewAction {
override fun getConstraints() = null 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 android.content.Context
import androidx.room.Room import androidx.room.Room
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.ligi.tracedroid.TraceDroid 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( val testiTestoInstance = InstanceDatabaseEntity(
uri = INSTANCE_URI, 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"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" 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.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -23,7 +23,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:replace="android:allowBackup"> tools:replace="android:allowBackup">
<activity android:name="com.h.pixeldroid.postCreation.camera.CameraActivity" /> <activity android:name=".postCreation.camera.CameraActivity" />
<activity <activity
android:name=".posts.ReportActivity" android:name=".posts.ReportActivity"
android:screenOrientation="sensorPortrait" android:screenOrientation="sensorPortrait"
@ -33,11 +33,26 @@
android:name=".postCreation.PostCreationActivity" android:name=".postCreation.PostCreationActivity"
android:screenOrientation="sensorPortrait" android:screenOrientation="sensorPortrait"
android:theme="@style/AppTheme.NoActionBar" 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 <activity
android:name=".profile.FollowsActivity" android:name=".profile.FollowsActivity"
android:screenOrientation="sensorPortrait" android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" /> tools:ignore="LockedOrientationActivity" />
<activity
android:name=".posts.feeds.uncachedFeeds.hashtags.HashTagActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" />
<activity <activity
android:name=".posts.PostActivity" android:name=".posts.PostActivity"
android:screenOrientation="sensorPortrait" android:screenOrientation="sensorPortrait"
@ -66,7 +81,7 @@
<meta-data <meta-data
android:name="android.app.default_searchable" android:name="android.app.default_searchable"
android:value=".searchDiscover.SearchActivity" /> android:value="org.pixeldroid.app.searchDiscover.SearchActivity" />
</activity> </activity>
<activity <activity
android:name=".LoginActivity" android:name=".LoginActivity"
@ -116,7 +131,7 @@
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="com.h.pixeldroid.fileprovider" android:authorities="${applicationId}.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data

View File

@ -818,9 +818,9 @@
</div> </div>
<div class="library"> <div class="library">
<!-- https://opensource.org/licenses/Apache-2.0 --> <!-- https://opensource.org/licenses/Apache-2.0 -->
<h1 class="title">kotlin-android-extensions-runtime</h1> <h1 class="title">startup-runtime</h1>
<p class="notice">Copyright &copy; JetBrains s.r.o. and contributors. All rights reserved.</p> <p class="notice">Copyright &copy; Google Inc. All rights reserved.</p>
<p><a href="https://kotlinlang.org/">https://kotlinlang.org/</a></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> <input type="checkbox"><label></label>
<div class="license"> <div class="license">
<h2> <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.app.AlertDialog
import android.content.Context import android.content.Context
@ -9,12 +9,12 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.databinding.ActivityLoginBinding import org.pixeldroid.app.databinding.ActivityLoginBinding
import com.h.pixeldroid.utils.* import org.pixeldroid.app.utils.*
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.* import org.pixeldroid.app.utils.api.objects.*
import com.h.pixeldroid.utils.db.addUser import org.pixeldroid.app.utils.db.addUser
import com.h.pixeldroid.utils.db.storeInstance import org.pixeldroid.app.utils.db.storeInstance
import kotlinx.coroutines.* import kotlinx.coroutines.*
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException 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) since they do not depend on each other)
_________________________________ _________________________________
|[PixelfedAPI.registerApplicationAsync]| |[PixelfedAPI.registerApplication]|
|[PixelfedAPI.wellKnownNodeInfo] | |[PixelfedAPI.wellKnownNodeInfo] |
̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅ +----> [PixelfedAPI.nodeInfoSchema] ̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅
+----> [promptOAuth] +----> [PixelfedAPI.nodeInfoSchema] (and then [PixelfedAPI.instance] if needed)
+---->____________________________ +----> [promptOAuth]
|[PixelfedAPI.instance] | +----> [PixelfedAPI.obtainToken]
|[PixelfedAPI.obtainToken] | +----> [PixelfedAPI.verifyCredentials]
̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅ +----> [PixelfedAPI.verifyCredentials]
*/ */
@ -311,7 +310,7 @@ class LoginActivity : BaseActivity() {
clientId = clientId, clientId = clientId,
clientSecret = clientSecret clientSecret = clientSecret
) )
apiHolder.setDomainToCurrentUser(db) apiHolder.setToCurrentUser()
val intent = Intent(this@LoginActivity, MainActivity::class.java) val intent = Intent(this@LoginActivity, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent) startActivity(intent)

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid package org.pixeldroid.app
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -14,22 +14,12 @@ import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi import androidx.paging.ExperimentalPagingApi
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator 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.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.materialdrawer.iconics.iconicsIcon import com.mikepenz.materialdrawer.iconics.iconicsIcon
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem 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.util.DrawerImageLoader
import com.mikepenz.materialdrawer.widget.AccountHeaderView import com.mikepenz.materialdrawer.widget.AccountHeaderView
import org.ligi.tracedroid.sending.sendTraceDroidStackTracesIfExist 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 retrofit2.HttpException
import java.io.IOException import java.io.IOException
class MainActivity : BaseActivity() { class MainActivity : BaseActivity() {
private lateinit var header: AccountHeaderView private lateinit var header: AccountHeaderView
@ -72,23 +78,22 @@ class MainActivity : BaseActivity() {
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this) sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
setupDrawer() setupDrawer()
val tabs: List<() -> Fragment> = listOf( val tabs: List<() -> Fragment> = listOf(
{ {
PostFeedFragment<HomeStatusDatabaseEntity>() PostFeedFragment<HomeStatusDatabaseEntity>()
.apply { .apply {
arguments = Bundle().apply { putBoolean("home", true) } arguments = Bundle().apply { putBoolean("home", true) }
} }
}, },
{ SearchDiscoverFragment() }, { SearchDiscoverFragment() },
{ CameraFragment() }, { CameraFragment() },
{ NotificationsFragment() }, { NotificationsFragment() },
{ {
PostFeedFragment<PublicFeedStatusDatabaseEntity>() PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply { .apply {
arguments = Bundle().apply { putBoolean("home", false) } arguments = Bundle().apply { putBoolean("home", false) }
} }
} }
) )
setupTabs(tabs) setupTabs(tabs)
} }
@ -177,7 +182,7 @@ class MainActivity : BaseActivity() {
} else { } else {
val newActive = remainingUsers.first() val newActive = remainingUsers.first()
db.userDao().activateUser(newActive.user_id) db.userDao().activateUser(newActive.user_id)
apiHolder.setDomainToCurrentUser(db) apiHolder.setToCurrentUser()
//relaunch the app //relaunch the app
launchActivity(MainActivity(), firstTime = true) launchActivity(MainActivity(), firstTime = true)
} }
@ -185,16 +190,17 @@ class MainActivity : BaseActivity() {
} }
private fun getUpdatedAccount() { private fun getUpdatedAccount() {
if (hasInternet(applicationContext)) { 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 { lifecycleScope.launchWhenCreated {
try { 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) addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
fillDrawerAccountInfo(account.id!!) fillDrawerAccountInfo(account.id!!)
} catch (exception: IOException) { } catch (exception: IOException) {
@ -220,7 +226,7 @@ class MainActivity : BaseActivity() {
db.userDao().deActivateActiveUsers() db.userDao().deActivateActiveUsers()
db.userDao().activateUser(profile.identifier.toString()) db.userDao().activateUser(profile.identifier.toString())
apiHolder.setDomainToCurrentUser(db) apiHolder.setToCurrentUser()
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent) startActivity(intent)
@ -268,8 +274,22 @@ class MainActivity : BaseActivity() {
header.setActiveProfile(account.toLong()) 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>){ private fun setupTabs(tab_array: List<() -> Fragment>){
binding.viewPager.reduceDragSensitivity()
binding.viewPager.adapter = object : FragmentStateAdapter(this) { binding.viewPager.adapter = object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return tab_array[position]() return tab_array[position]()
@ -279,6 +299,20 @@ class MainActivity : BaseActivity() {
return tab_array.size 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 -> TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.icon = ContextCompat.getDrawable(applicationContext, 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.Activity
import android.app.AlertDialog import android.app.AlertDialog
@ -16,29 +16,30 @@ import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.h.pixeldroid.MainActivity import org.pixeldroid.app.MainActivity
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ActivityPostCreationBinding import org.pixeldroid.app.databinding.ActivityPostCreationBinding
import com.h.pixeldroid.postCreation.camera.CameraActivity import org.pixeldroid.app.postCreation.camera.CameraActivity
import com.h.pixeldroid.postCreation.carousel.CarouselItem import org.pixeldroid.app.postCreation.carousel.CarouselItem
import com.h.pixeldroid.postCreation.carousel.ImageCarousel import org.pixeldroid.app.postCreation.carousel.ImageCarousel
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.objects.Attachment
import com.h.pixeldroid.utils.api.objects.Attachment import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import okhttp3.MultipartBody import okhttp3.MultipartBody
import retrofit2.HttpException import retrofit2.HttpException
import java.io.File import java.io.File
import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -58,9 +59,6 @@ data class PhotoData(
class PostCreationActivity : BaseActivity() { class PostCreationActivity : BaseActivity() {
private lateinit var accessToken: String
private lateinit var pixelfedAPI: PixelfedAPI
private var user: UserDatabaseEntity? = null private var user: UserDatabaseEntity? = null
private lateinit var instance: InstanceDatabaseEntity private lateinit var instance: InstanceDatabaseEntity
@ -86,9 +84,6 @@ class PostCreationActivity : BaseActivity() {
// get image URIs // get image URIs
intent.clipData?.let { addPossibleImages(it) } intent.clipData?.let { addPossibleImages(it) }
accessToken = user?.accessToken.orEmpty()
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val carousel: ImageCarousel = binding.carousel val carousel: ImageCarousel = binding.carousel
carousel.addData(photoData.map { CarouselItem(it.imageUri) }) carousel.addData(photoData.map { CarouselItem(it.imageUri) })
carousel.layoutCarouselCallback = { carousel.layoutCarouselCallback = {
@ -105,7 +100,7 @@ class PostCreationActivity : BaseActivity() {
addPhoto() addPhoto()
} }
carousel.updateDescriptionCallback = { position: Int, description: String -> carousel.updateDescriptionCallback = { position: Int, description: String ->
photoData[position].imageDescription = description photoData.getOrNull(position)?.imageDescription = description
} }
// get the description and send the post // get the description and send the post
@ -319,7 +314,16 @@ class PostCreationActivity : BaseActivity() {
for (data: PhotoData in photoData) { for (data: PhotoData in photoData) {
val imageUri = data.imageUri 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 imagePart = ProgressRequestBody(imageInputStream, data.size)
val requestBody = MultipartBody.Builder() val requestBody = MultipartBody.Builder()
@ -339,8 +343,8 @@ class PostCreationActivity : BaseActivity() {
val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) } val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) }
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", description, requestBody.parts[0]) val inter = api.mediaUpload(description, requestBody.parts[0])
postSub = inter postSub = inter
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -382,8 +386,9 @@ class PostCreationActivity : BaseActivity() {
enableButton(false) enableButton(false)
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
try { try {
pixelfedAPI.postStatus( val api = apiHolder.api ?: apiHolder.setToCurrentUser()
authorization = "Bearer $accessToken",
api.postStatus(
statusText = description, statusText = description,
media_ids = photoData.mapNotNull { it.uploadId }.toList() 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? -> result: ActivityResult? ->
if (result?.resultCode == Activity.RESULT_OK && result.data != null) { if (result?.resultCode == Activity.RESULT_OK && result.data != null) {
photoData[position].apply { val position: Int = result.data!!.getIntExtra(PhotoEditActivity.PICTURE_POSITION, 0)
imageUri = result.data!!.getStringExtra("result")!!.toUri() photoData.getOrNull(position)?.apply {
imageUri = result.data!!.getStringExtra(PhotoEditActivity.PICTURE_URI)!!.toUri()
size = imageUri.getSize() 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) }) 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){ } else if(result?.resultCode != Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
} }
@ -437,8 +442,8 @@ class PostCreationActivity : BaseActivity() {
private fun edit(position: Int) { private fun edit(position: Int) {
val intent = Intent(this, PhotoEditActivity::class.java) val intent = Intent(this, PhotoEditActivity::class.java)
.putExtra("picture_uri", photoData[position].imageUri) .putExtra(PhotoEditActivity.PICTURE_URI, photoData[position].imageUri)
.putExtra("no upload", false) .putExtra(PhotoEditActivity.PICTURE_POSITION, position)
editResultContract(position).launch(intent) 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.Observable
import io.reactivex.subjects.PublishSubject 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.content.Context
import android.util.AttributeSet 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 android.os.Bundle
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import com.h.pixeldroid.R import org.pixeldroid.app.R
class CameraActivity : BaseActivity() { class CameraActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -20,9 +20,4 @@ class CameraActivity : BaseActivity() {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.camera_activity_fragment, cameraFragment).commit() .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.Manifest
import android.app.Activity import android.app.Activity
@ -31,8 +31,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.postCreation.PostCreationActivity import org.pixeldroid.app.postCreation.PostCreationActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File 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.graphics.drawable.Drawable
import android.view.LayoutInflater import android.view.LayoutInflater
@ -9,7 +9,7 @@ import androidx.annotation.IdRes
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.h.pixeldroid.R import org.pixeldroid.app.R
class CarouselAdapter( class CarouselAdapter(
@ -96,8 +96,10 @@ class CarouselAdapter(
} }
fun updateDescription(position: Int, description: String) { fun updateDescription(position: Int, description: String) {
dataList[position] = dataList[position].copy(caption = description) dataList.getOrNull(position)?.apply {
notifyItemChanged(position) dataList[position] = copy(caption = description)
notifyItemChanged(position)
}
} }
fun addAll(dataList: List<CarouselItem>) { 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 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 android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager 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.content.Context
import android.graphics.Color import android.graphics.Color
@ -15,8 +15,8 @@ import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.* import androidx.recyclerview.widget.*
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ImageCarouselBinding import org.pixeldroid.app.databinding.ImageCarouselBinding
import me.relex.circleindicator.CircleIndicator2 import me.relex.circleindicator.CircleIndicator2
import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable import org.jetbrains.annotations.Nullable
@ -44,7 +44,6 @@ class ImageCarousel(
private lateinit var recyclerView: RecyclerView private lateinit var recyclerView: RecyclerView
private lateinit var tvCaption: TextView private lateinit var tvCaption: TextView
private lateinit var editTextMediaDescription: EditText
private var snapHelper: SnapHelper = PagerSnapHelper() private var snapHelper: SnapHelper = PagerSnapHelper()
var indicator: CircleIndicator2? = null var indicator: CircleIndicator2? = null
@ -292,9 +291,9 @@ class ImageCarousel(
if(layoutCarousel){ if(layoutCarousel){
field = value field = value
if(value) editTextMediaDescription.setText(currentDescription) if(value) binding.editTextMediaDescription.setText(currentDescription)
else { else {
val description = editTextMediaDescription.text.toString() val description = binding.editTextMediaDescription.text.toString()
currentDescription = description currentDescription = description
adapter?.updateDescription(currentPosition, description) adapter?.updateDescription(currentPosition, description)
updateDescriptionCallback?.invoke(currentPosition, description) updateDescriptionCallback?.invoke(currentPosition, description)
@ -339,7 +338,6 @@ class ImageCarousel(
recyclerView = binding.recyclerView recyclerView = binding.recyclerView
tvCaption = binding.tvCaption tvCaption = binding.tvCaption
editTextMediaDescription = binding.editTextMediaDescription
recyclerView.setHasFixedSize(true) 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.content.Context
import android.util.DisplayMetrics 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 android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -6,7 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.SeekBar import android.widget.SeekBar
import com.h.pixeldroid.R import org.pixeldroid.app.R
class EditImageFragment : Fragment(), SeekBar.OnSeekBarChangeListener { 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.Fragment
import androidx.fragment.app.FragmentManager 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.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
@ -11,7 +11,7 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.zomato.photofilters.FilterPack import com.zomato.photofilters.FilterPack
import com.zomato.photofilters.imageprocessors.Filter import com.zomato.photofilters.imageprocessors.Filter
import com.zomato.photofilters.utils.ThumbnailItem 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.content.Context
import android.util.AttributeSet 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.Activity
import android.app.AlertDialog import android.app.AlertDialog
@ -18,14 +18,11 @@ import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout import org.pixeldroid.app.R
import com.h.pixeldroid.R import org.pixeldroid.app.databinding.ActivityPhotoEditBinding
import com.h.pixeldroid.databinding.ActivityPhotoEditBinding import org.pixeldroid.app.postCreation.PostCreationActivity
import com.h.pixeldroid.databinding.ActivityPostCreationBinding import org.pixeldroid.app.utils.BaseActivity
import com.h.pixeldroid.postCreation.PostCreationActivity
import com.h.pixeldroid.utils.BaseActivity
import com.yalantis.ucrop.UCrop import com.yalantis.ucrop.UCrop
import com.zomato.photofilters.imageprocessors.Filter import com.zomato.photofilters.imageprocessors.Filter
import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter
@ -65,6 +62,8 @@ class PhotoEditActivity : BaseActivity() {
private lateinit var filterListFragment: FilterListFragment private lateinit var filterListFragment: FilterListFragment
private lateinit var editImageFragment: EditImageFragment private lateinit var editImageFragment: EditImageFragment
private var picturePosition: Int? = null
private var brightnessFinal = BRIGHTNESS_START private var brightnessFinal = BRIGHTNESS_START
private var saturationFinal = SATURATION_START private var saturationFinal = SATURATION_START
private var contrastFinal = CONTRAST_START private var contrastFinal = CONTRAST_START
@ -74,6 +73,9 @@ class PhotoEditActivity : BaseActivity() {
} }
companion object{ companion object{
internal const val PICTURE_URI = "picture_uri"
internal const val PICTURE_POSITION = "picture_position"
private var executor: ExecutorService = newSingleThreadExecutor() private var executor: ExecutorService = newSingleThreadExecutor()
private var future: Future<*>? = null private var future: Future<*>? = null
@ -97,7 +99,8 @@ class PhotoEditActivity : BaseActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true) supportActionBar?.setHomeButtonEnabled(true)
initialUri = intent.getParcelableExtra("picture_uri") initialUri = intent.getParcelableExtra(PICTURE_URI)
picturePosition = intent.getIntExtra(PICTURE_POSITION, 0)
imageUri = initialUri imageUri = initialUri
// Crop button on-click listener // Crop button on-click listener
@ -342,7 +345,8 @@ class PhotoEditActivity : BaseActivity() {
private fun sendBackImage(file: String) { private fun sendBackImage(file: String) {
val intent = Intent(this, PostCreationActivity::class.java) val intent = Intent(this, PostCreationActivity::class.java)
.apply { .apply {
putExtra("result", file) putExtra(PICTURE_URI, file)
putExtra(PICTURE_POSITION, picturePosition)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) 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.graphics.Rect
import android.view.View 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.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
@ -7,8 +7,8 @@ import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ThumbnailListItemBinding import org.pixeldroid.app.databinding.ThumbnailListItemBinding
import com.zomato.photofilters.utils.ThumbnailItem import com.zomato.photofilters.utils.ThumbnailItem
class ThumbnailAdapter (private val context: Context, 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.TextPaint
import android.text.style.ClickableSpan 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.content.Context
import android.os.Build import android.os.Build
@ -12,13 +12,13 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.text.toSpanned import androidx.core.text.toSpanned
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account.Companion.openAccountFromId import org.pixeldroid.app.utils.api.objects.Account.Companion.openAccountFromId
import com.h.pixeldroid.utils.api.objects.Mention import org.pixeldroid.app.utils.api.objects.Mention
import kotlinx.coroutines.coroutineScope import org.pixeldroid.app.utils.api.objects.Tag.Companion.openTag
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
import java.text.ParseException import java.text.ParseException
@ -50,12 +50,11 @@ fun getDomain(urlString: String?): String {
} }
fun parseHTMLText( fun parseHTMLText(
text : String, text: String,
mentions: List<Mention>?, mentions: List<Mention>?,
api : PixelfedAPI, apiHolder: PixelfedAPIHolder,
context: Context, context: Context,
credential: String, lifecycleScope: LifecycleCoroutineScope,
lifecycleScope: LifecycleCoroutineScope
) : Spanned { ) : Spanned {
//Convert text to spannable //Convert text to spannable
val content = fromHtml(text) val content = fromHtml(text)
@ -76,7 +75,7 @@ fun parseHTMLText(
val tag = text.subSequence(1, text.length).toString() val tag = text.subSequence(1, text.length).toString()
customSpan = object : ClickableSpanNoUnderline() { customSpan = object : ClickableSpanNoUnderline() {
override fun onClick(widget: View) { 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") Log.e("MENTION", "CLICKED")
//Retrieve the account for the given profile //Retrieve the account for the given profile
lifecycleScope.launchWhenCreated { 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. * limitations under the License.
*/ */
package com.h.pixeldroid.posts package org.pixeldroid.app.posts
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
@ -51,7 +51,7 @@ class NestedScrollableHost : ConstraintLayout {
return v as? ViewPager2 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 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) { if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x initialX = e.x
initialY = e.y initialY = e.y
doubleTapCallback?.invoke(Unit) doubleTapCallback?.invoke(true)
} }
// Early return if child can't scroll in same direction as parent // Early return if child can't scroll in same direction as parent
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) { if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
@ -94,10 +94,13 @@ class NestedScrollableHost : ConstraintLayout {
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child // assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f/ touchSlopModifier else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f 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 (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) { if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept // Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false) 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.content.Context
import android.os.Bundle import android.os.Bundle
@ -9,23 +9,22 @@ import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ActivityPostBinding import org.pixeldroid.app.databinding.ActivityPostBinding
import com.h.pixeldroid.databinding.CommentBinding import org.pixeldroid.app.databinding.CommentBinding
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Mention import org.pixeldroid.app.utils.api.objects.Mention
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_COMMENT_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import com.h.pixeldroid.utils.displayDimensionsInPx import org.pixeldroid.app.utils.displayDimensionsInPx
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class PostActivity : BaseActivity() { class PostActivity : BaseActivity() {
lateinit var domain : String lateinit var domain : String
private lateinit var accessToken : String
private lateinit var binding: ActivityPostBinding private lateinit var binding: ActivityPostBinding
@ -45,17 +44,15 @@ class PostActivity : BaseActivity() {
val user = db.userDao().getActiveUser() val user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty() domain = user?.instance_uri.orEmpty()
accessToken = user?.accessToken.orEmpty()
supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName()) supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName())
val holder = StatusViewHolder(binding.postFragmentSingle) 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()
activateCommenter(credential)
if(viewComments || postComment){ if(viewComments || postComment){
//Scroll already down as much as possible (since comments are not loaded yet) //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 // 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 { binding.postFragmentSingle.viewComments.setOnClickListener {
retrieveComments(apiHolder.api!!, credential) retrieveComments(apiHolder.api!!)
} }
} }
override fun onSupportNavigateUp(): Boolean { private fun activateCommenter() {
onBackPressed()
return true
}
private fun activateCommenter(credential: String) {
//Activate commenter //Activate commenter
binding.submitComment.setOnClickListener { binding.submitComment.setOnClickListener {
val textIn = binding.editComment.text val textIn = binding.editComment.text
@ -94,15 +86,14 @@ class PostActivity : BaseActivity() {
} else { } else {
//Post the comment //Post the comment
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
apiHolder.api?.let { it1 -> postComment(it1, credential) } apiHolder.api?.let { it1 -> postComment(it1) }
} }
} }
} }
} }
private fun addComment(context: Context, commentContainer: LinearLayout, private fun addComment(context: Context, commentContainer: LinearLayout,
commentUsername: String, commentContent: String, mentions: List<Mention>, commentUsername: String, commentContent: String, mentions: List<Mention>) {
credential: String) {
val itemBinding = CommentBinding.inflate( val itemBinding = CommentBinding.inflate(
@ -111,27 +102,30 @@ class PostActivity : BaseActivity() {
itemBinding.user.text = commentUsername itemBinding.user.text = commentUsername
itemBinding.commentText.text = parseHTMLText( itemBinding.commentText.text = parseHTMLText(
commentContent, commentContent,
mentions, mentions,
apiHolder.api!!, apiHolder,
context, context,
credential, lifecycleScope
lifecycleScope
) )
} }
private fun retrieveComments(api: PixelfedAPI, credential: String) { private fun retrieveComments(api: PixelfedAPI) {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
status.id.let { status.id.let {
try { try {
val statuses = api.statusComments(it, credential).descendants val statuses = api.statusComments(it).descendants
binding.commentContainer.removeAllViews() binding.commentContainer.removeAllViews()
//Create the new views for each comment //Create the new views for each comment
for (status in statuses) { for (status in statuses) {
addComment(binding.root.context, binding.commentContainer, status.account!!.username!!, addComment(
status.content!!, status.mentions.orEmpty(), credential binding.root.context,
binding.commentContainer,
status.account!!.username!!,
status.content!!,
status.mentions.orEmpty()
) )
} }
binding.commentContainer.visibility = View.VISIBLE binding.commentContainer.visibility = View.VISIBLE
@ -149,19 +143,18 @@ class PostActivity : BaseActivity() {
private suspend fun postComment( private suspend fun postComment(
api: PixelfedAPI, api: PixelfedAPI,
credential: String,
) { ) {
val textIn = binding.editComment.text val textIn = binding.editComment.text
val nonNullText = textIn.toString() val nonNullText = textIn.toString()
status.id.let { status.id.let {
try { try {
val response = api.postStatus(credential, nonNullText, it) val response = api.postStatus(nonNullText, it)
binding.commentIn.visibility = View.GONE binding.commentIn.visibility = View.GONE
//Add the comment to the comment section //Add the comment to the comment section
addComment( addComment(
binding.root.context, binding.commentContainer, response.account!!.username!!, binding.root.context, binding.commentContainer, response.account!!.username!!,
response.content!!, response.mentions.orEmpty(), credential response.content!!, response.mentions.orEmpty()
) )
Toast.makeText( Toast.makeText(

View File

@ -1,12 +1,12 @@
package com.h.pixeldroid.posts package org.pixeldroid.app.posts
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ActivityReportBinding import org.pixeldroid.app.databinding.ActivityReportBinding
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
@ -24,10 +24,6 @@ class ReportActivity : BaseActivity() {
val status = intent.getSerializableExtra(Status.POST_TAG) as Status? 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) binding.reportTargetTextview.text = getString(R.string.report_target).format(status?.account?.acct)
@ -37,12 +33,15 @@ class ReportActivity : BaseActivity() {
binding.textInputLayout.editText?.isEnabled = false binding.textInputLayout.editText?.isEnabled = false
val accessToken = user?.accessToken.orEmpty() val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
try { 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) reportStatus(true)
} catch (exception: IOException) { } catch (exception: IOException) {
@ -67,9 +66,4 @@ class ReportActivity : BaseActivity() {
binding.reportProgressBar.visibility = View.GONE 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.Manifest
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.util.Log import android.util.Log
@ -14,21 +15,23 @@ import android.widget.*
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.AlbumImageViewBinding import org.pixeldroid.app.databinding.AlbumImageViewBinding
import com.h.pixeldroid.databinding.PostFragmentBinding import org.pixeldroid.app.databinding.PostFragmentBinding
import com.h.pixeldroid.utils.BlurHashDecoder import org.pixeldroid.app.utils.BlurHashDecoder
import com.h.pixeldroid.utils.ImageConverter import org.pixeldroid.app.utils.ImageConverter
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Attachment import org.pixeldroid.app.utils.api.objects.Attachment
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_COMMENT_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import com.karumi.dexter.Dexter import com.karumi.dexter.Dexter
import com.karumi.dexter.listener.PermissionDeniedResponse import com.karumi.dexter.listener.PermissionDeniedResponse
import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.PermissionGrantedResponse
@ -46,7 +49,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
private var status: Status? = null 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.itemView.visibility = View.VISIBLE
this.status = status this.status = status
@ -177,21 +180,19 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
private fun setDescription( private fun setDescription(
api: PixelfedAPI, apiHolder: PixelfedAPIHolder,
credential: String, lifecycleScope: LifecycleCoroutineScope,
lifecycleScope: LifecycleCoroutineScope
) { ) {
binding.description.apply { binding.description.apply {
if (status?.content.isNullOrBlank()) { if (status?.content.isNullOrBlank()) {
visibility = View.GONE visibility = View.GONE
} else { } else {
text = parseHTMLText( text = parseHTMLText(
status?.content.orEmpty(), status?.content.orEmpty(),
status?.mentions, status?.mentions,
api, apiHolder,
binding.root.context, binding.root.context,
credential, lifecycleScope
lifecycleScope
) )
movementMethod = LinkMovementMethod.getInstance() movementMethod = LinkMovementMethod.getInstance()
} }
@ -199,25 +200,20 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
//region buttons //region buttons
private fun activateButtons( private fun activateButtons(
api: PixelfedAPI, apiHolder: PixelfedAPIHolder,
db: AppDatabase, db: AppDatabase,
lifecycleScope: LifecycleCoroutineScope, lifecycleScope: LifecycleCoroutineScope,
isActivity: Boolean isActivity: Boolean
){ ){
val user = db.userDao().getActiveUser()!!
val credential = "Bearer ${user.accessToken}"
//Set the special HTML text //Set the special HTML text
setDescription(api, credential, lifecycleScope) setDescription(apiHolder, lifecycleScope)
//Activate onclickListeners //Activate onclickListeners
activateLiker( activateLiker(
api, credential, status?.favourited ?: false, apiHolder, status?.favourited ?: false, lifecycleScope
lifecycleScope
) )
activateReblogger( activateReblogger(
api, credential, status?.reblogged ?: false, apiHolder, status?.reblogged ?: false, lifecycleScope
lifecycleScope
) )
if(isActivity){ if(isActivity){
@ -237,14 +233,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
showComments(lifecycleScope, isActivity) showComments(lifecycleScope, isActivity)
activateMoreButton(api, db, lifecycleScope) activateMoreButton(apiHolder, db, lifecycleScope)
} }
private fun activateReblogger( private fun activateReblogger(
api: PixelfedAPI, apiHolder: PixelfedAPIHolder,
credential: String, isReblogged: Boolean,
isReblogged: Boolean, lifecycleScope: LifecycleCoroutineScope,
lifecycleScope: LifecycleCoroutineScope
) { ) {
binding.reblogger.apply { binding.reblogger.apply {
//Set initial button state //Set initial button state
@ -253,12 +248,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate the button //Activate the button
setEventListener { _, buttonState -> setEventListener { _, buttonState ->
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
if (buttonState) { if (buttonState) {
// Button is active // Button is active
undoReblogPost(api, credential) undoReblogPost(api)
} else { } else {
// Button is inactive // Button is inactive
reblogPost(api, credential) reblogPost(api)
} }
} }
//show animation or not? //show animation or not?
@ -267,15 +263,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
private suspend fun reblogPost( private suspend fun reblogPost(api: PixelfedAPI) {
api: PixelfedAPI,
credential: String
) {
//Call the api function //Call the api function
status?.id?.let { status?.id?.let {
try { try {
val resp = api.reblogStatus(credential, it) val resp = api.reblogStatus(it)
//Update shown share count //Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context) binding.nshares.text = resp.getNShares(binding.root.context)
@ -290,14 +283,11 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
private suspend fun undoReblogPost( private suspend fun undoReblogPost(api: PixelfedAPI) {
api: PixelfedAPI,
credential: String,
) {
//Call the api function //Call the api function
status?.id?.let { status?.id?.let {
try { try {
val resp = api.undoReblogStatus(credential, it) val resp = api.undoReblogStatus(it)
//Update shown share count //Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context) 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 { binding.statusMore.setOnClickListener {
PopupMenu(it.context, it).apply { PopupMenu(it.context, it).apply {
setOnMenuItemClickListener { item -> setOnMenuItemClickListener { item ->
@ -395,7 +385,8 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
db.homePostDao().delete(id, user.user_id, user.instance_uri) db.homePostDao().delete(id, user.user_id, user.instance_uri)
db.publicPostDao().delete(id, user.user_id, user.instance_uri) db.publicPostDao().delete(id, user.user_id, user.instance_uri)
try { try {
api.deleteStatus("Bearer ${user.accessToken}", id) val api = apiHolder.api ?: apiHolder.setToCurrentUser()
api.deleteStatus(id)
binding.root.visibility = View.GONE binding.root.visibility = View.GONE
} catch (exception: HttpException) { } catch (exception: HttpException) {
Toast.makeText( Toast.makeText(
@ -439,10 +430,9 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
private fun activateLiker( private fun activateLiker(
api: PixelfedAPI, apiHolder: PixelfedAPIHolder,
credential: String, isLiked: Boolean,
isLiked: Boolean, lifecycleScope: LifecycleCoroutineScope,
lifecycleScope: LifecycleCoroutineScope
) { ) {
binding.liker.apply { binding.liker.apply {
@ -452,12 +442,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate the liker //Activate the liker
setEventListener { _, buttonState -> setEventListener { _, buttonState ->
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
if (buttonState) { if (buttonState) {
// Button is active, unlike // Button is active, unlike
unLikePostCall(api, credential) unLikePostCall(api)
} else { } else {
// Button is inactive, like // Button is inactive, like
likePostCall(api, credential) likePostCall(api)
} }
} }
//show animation or not? //show animation or not?
@ -468,20 +459,23 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate double tap liking //Activate double tap liking
var clicked = false var clicked = false
binding.postPagerHost.doubleTapCallback = { binding.postPagerHost.doubleTapCallback = {
lifecycleScope.launchWhenCreated { if(!it) clicked = false
else lifecycleScope.launchWhenCreated {
//Check that the post isn't hidden //Check that the post isn't hidden
if(binding.sensitiveWarning.visibility == View.GONE) { if(binding.sensitiveWarning.visibility == View.GONE) {
//Check for double click //Check for double click
if(clicked) { if(clicked) {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
if (binding.liker.isChecked) { if (binding.liker.isChecked) {
// Button is active, unlike // Button is active, unlike
binding.liker.isChecked = false binding.liker.isChecked = false
unLikePostCall(api, credential) unLikePostCall(api)
} else { } else {
// Button is inactive, like // Button is inactive, like
binding.liker.playAnimation() binding.liker.playAnimation()
binding.liker.isChecked = true binding.liker.isChecked = true
likePostCall(api, credential) binding.likeAnimation.animateView()
likePostCall(api)
} }
} else { } else {
clicked = true clicked = true
@ -490,20 +484,27 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
binding.postPager.handler.postDelayed(fun() { clicked = false }, 500) 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( private suspend fun likePostCall(api: PixelfedAPI) {
api: PixelfedAPI,
credential: String,
) {
//Call the api function //Call the api function
status?.id?.let { status?.id?.let {
try { try {
val resp = api.likePost(credential, it) val resp = api.likePost(it)
//Update shown like count and internal like toggle //Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context) binding.nlikes.text = resp.getNLikes(binding.root.context)
@ -518,15 +519,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
private suspend fun unLikePostCall( private suspend fun unLikePostCall(api: PixelfedAPI) {
api: PixelfedAPI,
credential: String,
) {
//Call the api function //Call the api function
status?.id?.let { status?.id?.let {
try { try {
val resp = api.unlikePost(credential, it) val resp = api.unlikePost(it)
//Update shown like count and internal like toggle //Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context) 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.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -12,14 +12,16 @@ import androidx.paging.LoadStateAdapter
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.h.pixeldroid.R import com.google.gson.Gson
import com.h.pixeldroid.databinding.ErrorLayoutBinding import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.LoadStateFooterViewItemBinding import org.pixeldroid.app.databinding.ErrorLayoutBinding
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel import org.pixeldroid.app.databinding.LoadStateFooterViewItemBinding
import com.h.pixeldroid.utils.api.objects.FeedContent import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import org.pixeldroid.app.utils.api.objects.FeedContent
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.HttpException
/** /**
* Shows or hides the error in the different FeedFragments * Shows or hides the error in the different FeedFragments
@ -56,24 +58,17 @@ internal fun <T: Any> initAdapter(
if(!progressBar.isVisible && swipeRefreshLayout.isRefreshing) { if(!progressBar.isVisible && swipeRefreshLayout.isRefreshing) {
// Stop loading spinner when loading is done // Stop loading spinner when loading is done
swipeRefreshLayout.isRefreshing = loadState.refresh is LoadState.Loading 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 val errorState = loadState.source.append as? LoadState.Error
?: loadState.source.prepend as? LoadState.Error ?: loadState.source.prepend as? LoadState.Error
?: loadState.source.refresh as? LoadState.Error ?: loadState.source.refresh as? LoadState.Error
@ -81,21 +76,37 @@ internal fun <T: Any> initAdapter(
?: loadState.prepend as? LoadState.Error ?: loadState.prepend as? LoadState.Error
?: loadState.refresh as? LoadState.Error ?: loadState.refresh as? LoadState.Error
errorState?.let { 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) { 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( fun <T: FeedContent> launch(
job: Job?, lifecycleScope: LifecycleCoroutineScope, viewModel: FeedViewModel<FeedContent>, job: Job?, lifecycleScope: LifecycleCoroutineScope, viewModel: FeedViewModel<T>,
pagingDataAdapter: PagingDataAdapter<FeedContent, RecyclerView.ViewHolder>): Job { pagingDataAdapter: PagingDataAdapter<T, RecyclerView.ViewHolder>): Job {
// Make sure we cancel the previous job before creating a new one // Make sure we cancel the previous job before creating a new one
job?.cancel() job?.cancel()
return lifecycleScope.launch { return lifecycleScope.launch {
viewModel.flow().collectLatest { viewModel.flow.collectLatest {
pagingDataAdapter.submitData(it) 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -8,21 +8,18 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.paging.* import androidx.paging.*
import androidx.paging.LoadState.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.collectLatest import org.pixeldroid.app.databinding.FragmentFeedBinding
import kotlinx.coroutines.flow.distinctUntilChangedBy import org.pixeldroid.app.posts.feeds.initAdapter
import kotlinx.coroutines.flow.filter import org.pixeldroid.app.utils.BaseFragment
import kotlinx.coroutines.launch import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.databinding.FragmentFeedBinding import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.limitedLengthSmoothScrollToPosition
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
/** /**
* A fragment representing a list of [FeedContentDatabase] items that are cached by the database. * 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 // Make sure we cancel the previous job before creating a new one
job?.cancel() job?.cancel()
job = lifecycleScope.launchWhenStarted { job = lifecycleScope.launchWhenStarted {
viewModel.flow().collectLatest { viewModel.flow.collectLatest {
adapter.submitData(it) adapter.submitData(it)
} }
} }
@ -51,12 +48,14 @@ open class CachedFeedFragment<T: FeedContentDatabase> : BaseFragment() {
internal fun initSearch() { internal fun initSearch() {
// Scroll to top when the list is refreshed from network. // Scroll to top when the list is refreshed from network.
lifecycleScope.launch { lifecycleScope.launchWhenStarted {
adapter.loadStateFlow adapter.loadStateFlow
// Only emit when REFRESH LoadState for RemoteMediator changes. // 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. // 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) } .collect { binding.list.scrollToPosition(0) }
} }
} }
@ -73,17 +72,16 @@ open class CachedFeedFragment<T: FeedContentDatabase> : BaseFragment() {
initAdapter(binding.progressBar, binding.swipeRefreshLayout, initAdapter(binding.progressBar, binding.swipeRefreshLayout,
binding.list, binding.motionLayout, binding.errorLayout, adapter) binding.list, binding.motionLayout, binding.errorLayout, adapter)
//binding.progressBar.visibility = View.GONE
binding.swipeRefreshLayout.setOnRefreshListener { 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() adapter.refresh()
} }
return binding.root return binding.root
} }
fun onTabReClicked() {
binding.list.limitedLengthSmoothScrollToPosition(0)
}
} }

View File

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

View File

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

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

View File

@ -14,13 +14,13 @@
* limitations under the License. * 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.paging.*
import androidx.room.withTransaction import androidx.room.withTransaction
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import com.h.pixeldroid.utils.api.objects.Notification import org.pixeldroid.app.utils.api.objects.Notification
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import java.lang.NullPointerException import java.lang.NullPointerException
@ -58,13 +58,12 @@ class NotificationsRemoteMediator @Inject constructor(
try { try {
val user = db.userDao().getActiveUser() val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists")) ?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val accessToken = user.accessToken
val apiResponse = api.notifications("Bearer $accessToken", val apiResponse = api.notifications(
max_id = max_id, max_id = max_id,
min_id = min_id, min_id = min_id,
limit = state.config.pageSize.toString(), limit = state.config.pageSize.toString(),
) )
apiResponse.forEach{it.user_id = user.user_id; it.instance_uri = user.instance_uri} 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.paging.*
import androidx.room.withTransaction import androidx.room.withTransaction
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import com.h.pixeldroid.utils.db.entities.HomeStatusDatabaseEntity import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import java.lang.NullPointerException import java.lang.NullPointerException
@ -43,12 +43,12 @@ class HomeFeedRemoteMediator @Inject constructor(
try { try {
val user = db.userDao().getActiveUser() val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists")) ?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) val api = apiHolder.api ?: apiHolder.setToCurrentUser()
val accessToken = user.accessToken
val apiResponse = api.timelineHome( "Bearer $accessToken", val apiResponse = api.timelineHome(
max_id= max_id, min_id = min_id, max_id= max_id,
limit = state.config.pageSize.toString()) min_id = min_id, limit = state.config.pageSize.toString()
)
val dbObjects = apiResponse.map{ val dbObjects = apiResponse.map{
HomeStatusDatabaseEntity(user.user_id, user.instance_uri, it) 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -11,15 +11,16 @@ import androidx.paging.PagingDataAdapter
import androidx.paging.RemoteMediator import androidx.paging.RemoteMediator
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.utils.db.dao.feedContent.FeedContentDao import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
import com.h.pixeldroid.posts.StatusViewHolder import org.pixeldroid.app.posts.StatusViewHolder
import com.h.pixeldroid.posts.feeds.cachedFeeds.FeedViewModel import org.pixeldroid.app.posts.feeds.cachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.cachedFeeds.CachedFeedFragment import org.pixeldroid.app.posts.feeds.cachedFeeds.CachedFeedFragment
import com.h.pixeldroid.posts.feeds.cachedFeeds.ViewModelFactory import org.pixeldroid.app.posts.feeds.cachedFeeds.ViewModelFactory
import com.h.pixeldroid.utils.api.objects.FeedContentDatabase import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import com.h.pixeldroid.utils.displayDimensionsInPx 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 mediator: RemoteMediator<Int, T>
private lateinit var dao: FeedContentDao<T> private lateinit var dao: FeedContentDao<T>
private var home by Delegates.notNull<Boolean>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
adapter = PostsAdapter(requireContext().displayDimensionsInPx()) adapter = PostsAdapter(requireContext().displayDimensionsInPx())
home = requireArguments().get("home") as Boolean
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
if (requireArguments().get("home") as Boolean){ if (home){
mediator = HomeFeedRemoteMediator(apiHolder, db) as RemoteMediator<Int, T> mediator = HomeFeedRemoteMediator(apiHolder, db) as RemoteMediator<Int, T>
dao = db.homePostDao() as FeedContentDao<T> dao = db.homePostDao() as FeedContentDao<T>
} }
@ -59,8 +63,8 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
// get the view model // get the view model
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory(db, dao, mediator)) viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(db, dao, mediator))
.get(FeedViewModel::class.java) as FeedViewModel<T> .get(if(home) "home" else "public", FeedViewModel::class.java) as FeedViewModel<T>
launch() launch()
initSearch() initSearch()
@ -70,12 +74,8 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<T, RecyclerView.ViewHolder>( inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<T, RecyclerView.ViewHolder>(
object : DiffUtil.ItemCallback<T>() { object : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { override fun areItemsTheSame (oldItem: T, newItem: T): Boolean = oldItem.id == newItem.id
return oldItem.id == newItem.id override fun areContentsTheSame(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) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status val uiModel = getItem(position) as Status?
uiModel.let { uiModel?.let {
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope, displayDimensionsInPx) (holder as StatusViewHolder).bind(it, apiHolder, db, lifecycleScope, displayDimensionsInPx)
} }
} }
} }

View File

@ -14,13 +14,13 @@
* limitations under the License. * 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.paging.*
import androidx.room.withTransaction import androidx.room.withTransaction
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.PublicFeedStatusDatabaseEntity import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
import com.h.pixeldroid.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import java.lang.NullPointerException import java.lang.NullPointerException
@ -58,7 +58,7 @@ class PublicFeedRemoteMediator @Inject constructor(
try { try {
val user = db.userDao().getActiveUser() val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists")) ?: 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( val apiResponse = api.timelinePublic(
max_id = max_id, 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -9,17 +9,17 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.paging.* import androidx.paging.*
import androidx.recyclerview.widget.RecyclerView 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.Job
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.h.pixeldroid.databinding.FragmentFeedBinding import org.pixeldroid.app.databinding.FragmentFeedBinding
import com.h.pixeldroid.utils.BaseFragment import org.pixeldroid.app.utils.BaseFragment
import com.h.pixeldroid.posts.feeds.initAdapter import org.pixeldroid.app.posts.feeds.initAdapter
import com.h.pixeldroid.utils.api.objects.FeedContent import org.pixeldroid.app.utils.api.objects.FeedContent
/** /**
@ -37,10 +37,7 @@ open class UncachedFeedFragment<T: FeedContent> : BaseFragment() {
internal fun launch() { internal fun launch() {
@Suppress("UNCHECKED_CAST") job = launch(job, lifecycleScope, viewModel, adapter)
job = launch(job, lifecycleScope,
viewModel as FeedViewModel<FeedContent>,
adapter as PagingDataAdapter<FeedContent, RecyclerView.ViewHolder>)
} }
internal fun initSearch() { internal fun initSearch() {
@ -68,9 +65,6 @@ open class UncachedFeedFragment<T: FeedContent> : BaseFragment() {
binding.motionLayout, binding.errorLayout, adapter) binding.motionLayout, binding.errorLayout, adapter)
binding.swipeRefreshLayout.setOnRefreshListener { 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() 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.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
@ -13,14 +13,14 @@ import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.AccountListEntryBinding import org.pixeldroid.app.databinding.AccountListEntryBinding
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedFeedFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.ViewModelFactory import org.pixeldroid.app.posts.feeds.uncachedFeeds.ViewModelFactory
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG
import com.h.pixeldroid.utils.api.objects.Account.Companion.FOLLOWERS_TAG import org.pixeldroid.app.utils.api.objects.Account.Companion.FOLLOWERS_TAG
/** /**
@ -52,16 +52,15 @@ class AccountListFragment : UncachedFeedFragment<Account>() {
// get the view model // get the view model
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory( viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
FollowersContentRepository( FollowersContentRepository(
apiHolder.setDomainToCurrentUser(db), apiHolder.setToCurrentUser(),
db.userDao().getActiveUser()!!.accessToken,
id, id,
following following
) )
) )
) )
.get(FeedViewModel::class.java) as FeedViewModel<Account> .get("accountList", FeedViewModel::class.java) as FeedViewModel<Account>
launch() launch()
initSearch() 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.ExperimentalPagingApi
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
@ -14,7 +14,6 @@ import javax.inject.Inject
class FollowersContentRepository @ExperimentalPagingApi class FollowersContentRepository @ExperimentalPagingApi
@Inject constructor( @Inject constructor(
private val api: PixelfedAPI, private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String, private val accountId: String,
private val following: Boolean, private val following: Boolean,
): UncachedContentRepository<Account> { ): UncachedContentRepository<Account> {
@ -25,7 +24,7 @@ class FollowersContentRepository @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE, pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false), enablePlaceholders = false),
pagingSourceFactory = { pagingSourceFactory = {
FollowersPagingSource(api, accessToken, accountId, following) FollowersPagingSource(api, accountId, following)
} }
).flow ).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.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import java.math.BigInteger
class FollowersPagingSource( class FollowersPagingSource(
private val api: PixelfedAPI, private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String, private val accountId: String,
private val following: Boolean private val following: Boolean
) : PagingSource<String, Account>() { ) : PagingSource<String, Account>() {
@ -22,17 +20,19 @@ class FollowersPagingSource(
// Laravel's paging mechanism, while Mastodon uses the Link header for pagination. // 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 // No need to know which is which, they should ignore the non-relevant argument
if(following) { if(following) {
api.followers(account_id = accountId, api.followers(
authorization = "Bearer $accessToken", account_id = accountId,
max_id = position,
limit = params.loadSize, limit = params.loadSize,
page = position, page = position
max_id = position) )
} else { } else {
api.following(account_id = accountId, api.following(
authorization = "Bearer $accessToken", account_id = accountId,
max_id = position,
limit = params.loadSize, limit = params.loadSize,
page = position, page = position
max_id = position) )
} }
val accounts = if(response.isSuccessful){ val accounts = if(response.isSuccessful){
@ -41,25 +41,22 @@ class FollowersPagingSource(
throw HttpException(response) throw HttpException(response)
} }
val nextPosition: String = if(response.headers()["Link"] != null){ val nextPosition: String = response.headers()["Link"]
//Header is of the form: // 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" // 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 // 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 '?' // the max_id in the URL, we make sure to stop at the first '?'
response.headers()["Link"] ?.substringAfter("max_id=", "")
.orEmpty() ?.substringBefore('?', "")
.substringAfter("max_id=", "") ?.substringBefore('>', "")
.substringBefore('?', "")
.substringBefore('>', "") ?: // No Link header, so we just increment the position value (Pixelfed case)
} else {
// No Link header, so we just increment the position value
(position?.toBigIntegerOrNull() ?: 1.toBigInteger()).inc().toString() (position?.toBigIntegerOrNull() ?: 1.toBigInteger()).inc().toString()
}
LoadResult.Page( LoadResult.Page(
data = accounts, data = accounts,
prevKey = null, prevKey = null,
nextKey = if (accounts.isEmpty()) null else nextPosition nextKey = if (accounts.isEmpty() || nextPosition.isEmpty() || nextPosition == position) null else nextPosition
) )
} catch (exception: IOException) { } catch (exception: IOException) {
LoadResult.Error(exception) LoadResult.Error(exception)
@ -68,8 +65,5 @@ class FollowersPagingSource(
} }
} }
override fun getRefreshKey(state: PagingState<String, Account>): String? = override fun getRefreshKey(state: PagingState<String, Account>): String? = null
state.anchorPosition?.run {
state.closestItemToPosition(this)?.id
}
} }

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.ExperimentalPagingApi
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
class ProfileContentRepository @ExperimentalPagingApi class ProfileContentRepository @ExperimentalPagingApi
@Inject constructor( @Inject constructor(
private val api: PixelfedAPI, private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String private val accountId: String
) : UncachedContentRepository<Status> { ) : UncachedContentRepository<Status> {
override fun getStream(): Flow<PagingData<Status>> { override fun getStream(): Flow<PagingData<Status>> {
@ -23,7 +22,7 @@ class ProfileContentRepository @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE, pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false), enablePlaceholders = false),
pagingSourceFactory = { pagingSourceFactory = {
ProfilePagingSource(api, accessToken, accountId) ProfilePagingSource(api, accountId)
} }
).flow ).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.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class ProfilePagingSource( class ProfilePagingSource(
private val api: PixelfedAPI, private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String private val accountId: String
) : PagingSource<String, Status>() { ) : PagingSource<String, Status>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> { override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
val position = params.key val position = params.key
return try { return try {
val posts = api.accountPosts("Bearer $accessToken", val posts = api.accountPosts(
account_id = accountId, account_id = accountId,
max_id = position, max_id = position,
limit = params.loadSize limit = params.loadSize
) )
val nextKey = posts.lastOrNull()?.id
LoadResult.Page( LoadResult.Page(
data = posts, data = posts,
prevKey = null, prevKey = null,
nextKey = posts.lastOrNull()?.id nextKey = if(nextKey == position) null else nextKey
) )
} catch (exception: HttpException) { } catch (exception: HttpException) {
LoadResult.Error(exception) 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -6,10 +6,10 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.paging.ExperimentalPagingApi import androidx.paging.ExperimentalPagingApi
import com.h.pixeldroid.posts.feeds.uncachedFeeds.* import org.pixeldroid.app.posts.feeds.uncachedFeeds.*
import com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists.AccountAdapter import org.pixeldroid.app.posts.feeds.uncachedFeeds.accountLists.AccountAdapter
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Results import org.pixeldroid.app.utils.api.objects.Results
/** /**
* Fragment to show a list of [Account]s, as a result of a search. * 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 // get the view model
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory( viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
SearchContentRepository<Account>( SearchContentRepository<Account>(
apiHolder.setDomainToCurrentUser(db), apiHolder.setToCurrentUser(),
Results.SearchType.accounts, Results.SearchType.accounts,
db.userDao().getActiveUser()!!.accessToken,
query query
) )
) )
).get(FeedViewModel::class.java) as FeedViewModel<Account> ).get("searchAccounts", FeedViewModel::class.java) as FeedViewModel<Account>
launch() launch()
initSearch() 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.ExperimentalPagingApi
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.utils.api.objects.FeedContent import org.pixeldroid.app.utils.api.objects.FeedContent
import com.h.pixeldroid.utils.api.objects.Results import org.pixeldroid.app.utils.api.objects.Results
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
@ -15,14 +15,13 @@ import javax.inject.Inject
* Repository class to perform searches * Repository class to perform searches
* *
* The type argument [T] and the [Results.SearchType][type] argument should always * 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]. * [type] should be [Results.SearchType.accounts].
*/ */
class SearchContentRepository<T: FeedContent> @ExperimentalPagingApi class SearchContentRepository<T: FeedContent> @ExperimentalPagingApi
@Inject constructor( @Inject constructor(
private val api: PixelfedAPI, private val api: PixelfedAPI,
private val type: Results.SearchType, private val type: Results.SearchType,
private val accessToken: String,
private val query: String, private val query: String,
): UncachedContentRepository<T> { ): UncachedContentRepository<T> {
override fun getStream(): Flow<PagingData<T>> { override fun getStream(): Flow<PagingData<T>> {
@ -32,7 +31,7 @@ class SearchContentRepository<T: FeedContent> @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE, pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false), enablePlaceholders = false),
pagingSourceFactory = { pagingSourceFactory = {
SearchPagingSource<T>(api, query, type, accessToken) SearchPagingSource<T>(api, query, type)
} }
).flow ).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.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
@ -11,13 +11,14 @@ import androidx.paging.ExperimentalPagingApi
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.FragmentTagsBinding import org.pixeldroid.app.databinding.FragmentTagsBinding
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedFeedFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.ViewModelFactory import org.pixeldroid.app.posts.feeds.uncachedFeeds.ViewModelFactory
import com.h.pixeldroid.utils.api.objects.Results import org.pixeldroid.app.utils.api.objects.Results
import com.h.pixeldroid.utils.api.objects.Tag 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. * 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 // get the view model
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ViewModelFactory( viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
SearchContentRepository<Tag>( SearchContentRepository<Tag>(
apiHolder.setDomainToCurrentUser(db), apiHolder.setToCurrentUser(),
Results.SearchType.hashtags, Results.SearchType.hashtags,
db.userDao().getActiveUser()!!.accessToken,
query query
) )
) )
) )
.get(FeedViewModel::class.java) as FeedViewModel<Tag> .get("searchHashtag", FeedViewModel::class.java) as FeedViewModel<Tag>
launch() launch()
initSearch() initSearch()
@ -87,7 +87,7 @@ class HashTagAdapter : PagingDataAdapter<Tag, RecyclerView.ViewHolder>(
companion object { companion object {
private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback<Tag>() { private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback<Tag>() {
override fun areItemsTheSame(oldItem: Tag, newItem: Tag): Boolean { 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 = override fun areContentsTheSame(oldItem: Tag, newItem: Tag): Boolean =
@ -108,7 +108,9 @@ class HashTagViewHolder(binding: FragmentTagsBinding) : RecyclerView.ViewHolder(
init { init {
itemView.setOnClickListener { 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.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.FeedContent import org.pixeldroid.app.utils.api.objects.FeedContent
import com.h.pixeldroid.utils.api.objects.Results import org.pixeldroid.app.utils.api.objects.Results
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
@ -15,16 +15,16 @@ class SearchPagingSource<T: FeedContent>(
private val api: PixelfedAPI, private val api: PixelfedAPI,
private val query: String, private val query: String,
private val type: Results.SearchType, private val type: Results.SearchType,
private val accessToken: String,
) : PagingSource<Int, T>() { ) : PagingSource<Int, T>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val position = params.key val position = params.key
return try { return try {
val response = api.search(authorization = "Bearer $accessToken", val response = api.search(
offset = position?.toString(),
q = query,
type = type, type = type,
limit = params.loadSize.toString()) q = query,
limit = params.loadSize.toString(),
offset = position?.toString()
)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -34,10 +34,12 @@ class SearchPagingSource<T: FeedContent>(
Results.SearchType.statuses -> response.statuses Results.SearchType.statuses -> response.statuses
} as List<T> } as List<T>
val nextKey = if (repos.isEmpty()) null else (position ?: 0) + repos.size
LoadResult.Page( LoadResult.Page(
data = repos, data = repos,
prevKey = null, prevKey = null,
nextKey = if (repos.isEmpty()) null else (position ?: 0) + repos.size nextKey = if(nextKey == position) null else nextKey
) )
} catch (exception: HttpException) { } catch (exception: HttpException) {
LoadResult.Error(exception) LoadResult.Error(exception)
@ -46,8 +48,9 @@ class SearchPagingSource<T: FeedContent>(
} }
} }
override fun getRefreshKey(state: PagingState<Int, T>): Int? = /**
state.anchorPosition?.run { * FIXME if implemented with [PagingState.anchorPosition], this breaks refreshes? How is this
state.closestItemToPosition(this)?.id?.toIntOrNull() * 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 android.os.Bundle
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists.AccountListFragment import org.pixeldroid.app.posts.feeds.uncachedFeeds.accountLists.AccountListFragment
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_TAG import org.pixeldroid.app.utils.api.objects.Account.Companion.ACCOUNT_TAG
import com.h.pixeldroid.utils.api.objects.Account.Companion.FOLLOWERS_TAG import org.pixeldroid.app.utils.api.objects.Account.Companion.FOLLOWERS_TAG
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
class FollowsActivity : 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) { private fun startFragment(id : String, displayName: String, followers : Boolean) {
supportActionBar?.title = supportActionBar?.title =
if (followers) { if (followers) {

View File

@ -1,4 +1,4 @@
package com.h.pixeldroid.profile package org.pixeldroid.app.profile
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -18,34 +19,31 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ActivityProfileBinding import org.pixeldroid.app.databinding.ActivityProfileBinding
import com.h.pixeldroid.databinding.FragmentProfilePostsBinding import org.pixeldroid.app.databinding.FragmentProfilePostsBinding
import com.h.pixeldroid.posts.PostActivity import org.pixeldroid.app.posts.PostActivity
import com.h.pixeldroid.posts.feeds.initAdapter import org.pixeldroid.app.posts.feeds.initAdapter
import com.h.pixeldroid.posts.feeds.launch import org.pixeldroid.app.posts.feeds.launch
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import com.h.pixeldroid.posts.feeds.uncachedFeeds.profile.ProfileContentRepository import org.pixeldroid.app.posts.feeds.uncachedFeeds.profile.ProfileContentRepository
import com.h.pixeldroid.posts.parseHTMLText import org.pixeldroid.app.posts.parseHTMLText
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import com.h.pixeldroid.utils.ImageConverter import org.pixeldroid.app.utils.ImageConverter
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.FeedContent import org.pixeldroid.app.utils.api.objects.FeedContent
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.utils.openUrl import org.pixeldroid.app.utils.openUrl
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class ProfileActivity : BaseActivity() { class ProfileActivity : BaseActivity() {
private lateinit var pixelfedAPI : PixelfedAPI
private lateinit var accessToken : String
private lateinit var domain : String private lateinit var domain : String
private lateinit var accountId : String private lateinit var accountId : String
private lateinit var binding: ActivityProfileBinding private lateinit var binding: ActivityProfileBinding
@ -66,8 +64,6 @@ class ProfileActivity : BaseActivity() {
user = db.userDao().getActiveUser() user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty() domain = user?.instance_uri.orEmpty()
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
accessToken = user?.accessToken.orEmpty()
// Set profile according to given account // Set profile according to given account
val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account? val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account?
@ -77,9 +73,8 @@ class ProfileActivity : BaseActivity() {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ProfileViewModelFactory( viewModel = ViewModelProvider(this, ProfileViewModelFactory(
ProfileContentRepository( ProfileContentRepository(
apiHolder.setDomainToCurrentUser(db), apiHolder.setToCurrentUser(),
db.userDao().getActiveUser()!!.accessToken, accountId
accountId
) )
) )
).get(FeedViewModel::class.java) as FeedViewModel<Status> ).get(FeedViewModel::class.java) as FeedViewModel<Status>
@ -96,9 +91,7 @@ class ProfileActivity : BaseActivity() {
} }
setContent(account) setContent(account)
@Suppress("UNCHECKED_CAST") job = launch(job, lifecycleScope, viewModel, profileAdapter)
job = launch(job, lifecycleScope, viewModel as FeedViewModel<FeedContent>,
profileAdapter as PagingDataAdapter<FeedContent, RecyclerView.ViewHolder>)
} }
/** /**
@ -114,18 +107,14 @@ class ProfileActivity : BaseActivity() {
binding.profileRefreshLayout.isRefreshing = false binding.profileRefreshLayout.isRefreshing = false
} }
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun setContent(account: Account?) { private fun setContent(account: Account?) {
if(account != null) { if(account != null) {
setViews(account) setViews(account)
} else { } else {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
val myAccount: Account = try { val myAccount: Account = try {
pixelfedAPI.verifyCredentials("Bearer $accessToken") api.verifyCredentials()
} catch (exception: IOException) { } catch (exception: IOException) {
Log.e("ProfileActivity:", exception.toString()) Log.e("ProfileActivity:", exception.toString())
return@launchWhenResumed showError() return@launchWhenResumed showError()
@ -162,9 +151,9 @@ class ProfileActivity : BaseActivity() {
) )
binding.descriptionTextView.text = parseHTMLText( binding.descriptionTextView.text = parseHTMLText(
account.note ?: "", emptyList(), pixelfedAPI, account.note ?: "", emptyList(), apiHolder,
applicationContext, "Bearer $accessToken", applicationContext,
lifecycleScope lifecycleScope
) )
val displayName = account.getDisplayName() val displayName = account.getDisplayName()
@ -235,13 +224,14 @@ class ProfileActivity : BaseActivity() {
// Get relationship between the two users (credential and this) and set followButton accordingly // Get relationship between the two users (credential and this) and set followButton accordingly
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val relationship = pixelfedAPI.checkRelationships( val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
"Bearer $accessToken", listOf(account.id.orEmpty()) val relationship = api.checkRelationships(
listOf(account.id.orEmpty())
).firstOrNull() ).firstOrNull()
if(relationship != null){ if(relationship != null){
if (relationship.following) { if (relationship.following == true || relationship.requested == true) {
setOnClickUnfollow(account) setOnClickUnfollow(account, relationship.requested == true)
} else { } else {
setOnClickFollow(account) setOnClickFollow(account)
} }
@ -268,8 +258,10 @@ class ProfileActivity : BaseActivity() {
setOnClickListener { setOnClickListener {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
try { try {
pixelfedAPI.follow(account.id.orEmpty(), "Bearer $accessToken") val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
setOnClickUnfollow(account) val rel = api.follow(account.id.orEmpty())
if(rel.following == true) setOnClickUnfollow(account, rel.requested == true)
else setOnClickFollow(account)
} catch (exception: IOException) { } catch (exception: IOException) {
Log.e("FOLLOW ERROR", exception.toString()) Log.e("FOLLOW ERROR", exception.toString())
Toast.makeText( Toast.makeText(
@ -287,29 +279,46 @@ class ProfileActivity : BaseActivity() {
} }
} }
private fun setOnClickUnfollow(account: Account) { private fun setOnClickUnfollow(account: Account, requested: Boolean) {
binding.followButton.apply { 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 { lifecycleScope.launchWhenResumed {
try { try {
pixelfedAPI.unfollow(account.id.orEmpty(), "Bearer $accessToken") val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
setOnClickFollow(account) 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) { } catch (exception: IOException) {
Log.e("FOLLOW ERROR", exception.toString()) Log.e("FOLLOW ERROR", exception.toString())
Toast.makeText( Toast.makeText(
applicationContext, getString(R.string.unfollow_error), applicationContext, getString(R.string.unfollow_error),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} catch (exception: HttpException) { } catch (exception: HttpException) {
Toast.makeText( Toast.makeText(
applicationContext, getString(R.string.unfollow_error), applicationContext, getString(R.string.unfollow_error),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).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.content.Intent
import android.view.LayoutInflater import android.view.LayoutInflater
@ -7,11 +7,11 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.posts.PostActivity import org.pixeldroid.app.posts.PostActivity
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import com.h.pixeldroid.utils.ImageConverter.Companion.setSquareImageFromDrawable import org.pixeldroid.app.utils.ImageConverter.Companion.setSquareImageFromDrawable
import com.h.pixeldroid.utils.ImageConverter.Companion.setSquareImageFromURL import org.pixeldroid.app.utils.ImageConverter.Companion.setSquareImageFromURL
/** /**
* [RecyclerView.Adapter] that can display a list of [Status]s * [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.app.SearchManager
import android.content.Intent import android.content.Intent
@ -8,12 +8,12 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.posts.feeds.uncachedFeeds.search.SearchAccountFragment import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedPostsFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.search.SearchHashtagFragment import org.pixeldroid.app.posts.feeds.uncachedFeeds.search.SearchAccountFragment
import com.h.pixeldroid.posts.feeds.uncachedFeeds.search.SearchPostsFragment import org.pixeldroid.app.posts.feeds.uncachedFeeds.search.SearchHashtagFragment
import com.h.pixeldroid.utils.api.objects.Results import org.pixeldroid.app.utils.api.objects.Results
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
class SearchActivity : BaseActivity() { class SearchActivity : BaseActivity() {
@ -47,14 +47,9 @@ class SearchActivity : BaseActivity() {
setupTabs(tabs, searchType) setupTabs(tabs, searchType)
} }
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun createSearchTabs(query: String): Array<Fragment>{ private fun createSearchTabs(query: String): Array<Fragment>{
val searchFeedFragment = SearchPostsFragment() val searchFeedFragment = UncachedPostsFragment()
val searchAccountListFragment = val searchAccountListFragment =
SearchAccountFragment() SearchAccountFragment()
val searchHashtagFragment: Fragment = SearchHashtagFragment() 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.app.SearchManager
import android.content.Context import android.content.Context
@ -11,21 +11,15 @@ import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.FragmentSearchBinding import org.pixeldroid.app.databinding.FragmentSearchBinding
import com.h.pixeldroid.profile.ProfilePostViewHolder import org.pixeldroid.app.profile.ProfilePostViewHolder
import com.h.pixeldroid.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Status
import com.h.pixeldroid.posts.PostActivity import org.pixeldroid.app.posts.PostActivity
import com.h.pixeldroid.utils.BaseFragment import org.pixeldroid.app.utils.BaseFragment
import com.h.pixeldroid.utils.ImageConverter import org.pixeldroid.app.utils.ImageConverter
import com.h.pixeldroid.utils.bindingLifecycleAware import org.pixeldroid.app.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 retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
@ -37,7 +31,6 @@ class SearchDiscoverFragment : BaseFragment() {
private lateinit var api: PixelfedAPI private lateinit var api: PixelfedAPI
private lateinit var recycler : RecyclerView private lateinit var recycler : RecyclerView
private lateinit var adapter : DiscoverRecyclerViewAdapter private lateinit var adapter : DiscoverRecyclerViewAdapter
private lateinit var accessToken: String
var binding: FragmentSearchBinding by bindingLifecycleAware() var binding: FragmentSearchBinding by bindingLifecycleAware()
@ -66,9 +59,7 @@ class SearchDiscoverFragment : BaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) api = apiHolder.api ?: apiHolder.setToCurrentUser()
accessToken = db.userDao().getActiveUser()?.accessToken.orEmpty()
getDiscover() getDiscover()
@ -93,8 +84,9 @@ class SearchDiscoverFragment : BaseFragment() {
private fun getDiscover() { private fun getDiscover() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
try { try {
val discoverPosts = api.discover("Bearer $accessToken") val discoverPosts = api.discover()
adapter.addPosts(discoverPosts.posts) adapter.addPosts(discoverPosts.posts)
binding.discoverNoInfiniteLoad.visibility = View.VISIBLE
showError(show = false) showError(show = false)
} catch (exception: IOException) { } catch (exception: IOException) {
showError() showError()
@ -108,7 +100,7 @@ class SearchDiscoverFragment : BaseFragment() {
* [RecyclerView.Adapter] that can display a list of [Status]s' thumbnails for the discover view * [RecyclerView.Adapter] that can display a list of [Status]s' thumbnails for the discover view
*/ */
class DiscoverRecyclerViewAdapter: RecyclerView.Adapter<ProfilePostViewHolder>() { class DiscoverRecyclerViewAdapter: RecyclerView.Adapter<ProfilePostViewHolder>() {
private val posts: ArrayList<Status> = ArrayList() private val posts: ArrayList<Status?> = ArrayList()
fun addPosts(newPosts : List<Status>) { fun addPosts(newPosts : List<Status>) {
posts.clear() posts.clear()
@ -124,12 +116,12 @@ class SearchDiscoverFragment : BaseFragment() {
override fun onBindViewHolder(holder: ProfilePostViewHolder, position: Int) { override fun onBindViewHolder(holder: ProfilePostViewHolder, position: Int) {
val post = posts[position] val post = posts[position]
if(post.media_attachments?.size ?: 0 > 1) { if(post?.media_attachments?.size ?: 0 > 1) {
holder.albumIcon.visibility = View.VISIBLE holder.albumIcon.visibility = View.VISIBLE
} else { } else {
holder.albumIcon.visibility = View.GONE 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 { holder.postPreview.setOnClickListener {
val intent = Intent(holder.postView.context, PostActivity::class.java) val intent = Intent(holder.postView.context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post) 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.content.Intent
import android.os.Bundle import android.os.Bundle
import com.h.pixeldroid.BuildConfig import org.pixeldroid.app.BuildConfig
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ActivityAboutBinding import org.pixeldroid.app.databinding.ActivityAboutBinding
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
class AboutActivity : BaseActivity() { class AboutActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { 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 android.os.Bundle
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.databinding.ActivityLicensesBinding import org.pixeldroid.app.databinding.ActivityLicensesBinding
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
class LicenseActivity : BaseActivity() { class LicenseActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { 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.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.h.pixeldroid.MainActivity import org.pixeldroid.app.MainActivity
import com.h.pixeldroid.R import org.pixeldroid.app.R
import com.h.pixeldroid.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import com.h.pixeldroid.utils.setThemeFromPreferences import org.pixeldroid.app.utils.setThemeFromPreferences
class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener { class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
private var restartMainOnExit = false 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.Context
import android.content.res.Configuration import android.content.res.Configuration
@ -7,8 +7,8 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -28,6 +28,11 @@ open class BaseActivity : AppCompatActivity() {
super.attachBaseContext(updateBaseContextLocale(base)) super.attachBaseContext(updateBaseContextLocale(base))
} }
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun updateBaseContextLocale(context: Context): Context { private fun updateBaseContextLocale(context: Context): Context {
val language = PreferenceManager.getDefaultSharedPreferences(context).getString("language", "default") ?: "default" val language = PreferenceManager.getDefaultSharedPreferences(context).getString("language", "default") ?: "default"
if(language == "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 android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.h.pixeldroid.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import javax.inject.Inject 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: * Blurhash implementation from blurhash project:
@ -11,7 +11,6 @@ import android.content.res.Resources
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import com.h.pixeldroid.utils.api.objects.Attachment
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.pow 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.graphics.drawable.Drawable
import android.view.View import android.view.View
@ -8,7 +8,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.h.pixeldroid.R import org.pixeldroid.app.R
class ImageConverter { class ImageConverter {
companion object { companion object {

View File

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

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