Compare commits
75 Commits
1.0.beta26
...
master
Author | SHA1 | Date |
---|---|---|
Matthieu | 187c29a751 | |
Balaraz | f9a07a5dd0 | |
Alexandre NICOLADIE | fb9296187a | |
Matthieu | 04324577ea | |
Matthieu | 73f08e5a5f | |
Matthieu | a7feab380b | |
Matthieu | 1a4e023091 | |
Regu_Miabyss | 2fb4c91ffd | |
Matthieu | 720c099f0a | |
Matthieu | 869479d53f | |
Matthieu | 018f893388 | |
Matthieu | f27ae611be | |
Matthieu | 234be72f59 | |
Matthieu | 7eaac2e903 | |
Matthieu | a9849c13e6 | |
Matthieu | d1562f18e9 | |
Matthieu | 4a1248bcab | |
Regu_Miabyss | e9122e6d72 | |
Matthieu | 76eac62d73 | |
Matthieu | dd27555d83 | |
Matthieu | c0feb8a37d | |
Matthieu | 7acd4cface | |
Matthieu | 10e93c90b7 | |
Matthieu | 2311627473 | |
Regu_Miabyss | 6c7ab2333e | |
Matthieu | 54711d4e81 | |
Matthieu | 908d1a54c9 | |
Matthieu | 2b91543137 | |
Matthieu | 0688dd4d02 | |
Matthieu | 319da7c11c | |
Matthieu | 7b327fc0d6 | |
Matthieu | 64ab2c2ac5 | |
Matthieu | 37b83f5ae2 | |
Matthieu | 1516452ab5 | |
Matthieu | cb50db7730 | |
Matthieu | afe6f71152 | |
Matthieu | d66c365934 | |
Matthieu | 7815ecba08 | |
Matthieu | b4533014b3 | |
Matthieu | 905c1c2d66 | |
Matthieu | 46ee92a19f | |
Matthieu | ed7ff877fb | |
Francesco Marinucci | 5c3b231e16 | |
Matthieu | 80e021e1a2 | |
Matthieu | 0aa3d86c11 | |
Matthieu | 05cb615f15 | |
Matthieu | 9313f321cd | |
Matthieu | 175438115d | |
Matthieu | f2600b85e2 | |
Matthieu | 711a5b310f | |
Francesco Marinucci | 0192190c6d | |
Matthieu | bb935e73ad | |
Matthieu | ddf1b273de | |
Matthieu | 14dee5463e | |
Matthieu | 416d36b1a8 | |
Matthieu | 370aeda4a6 | |
Matthieu | eb65e24099 | |
Fred | 1ea4371b3e | |
Matthieu | 2e399884e9 | |
Matthieu | 1dcf605976 | |
Fred | 06478cf8a7 | |
Fred | 580f7ca911 | |
Matthieu | 23fbebfe44 | |
Fred | 6602d912a9 | |
Matthieu | 1f41268d55 | |
Francesco Marinucci | 15efdcabad | |
Matthieu | 97069a76db | |
Francesco Marinucci | 834b3f86bd | |
ButterflyOfFire | adc4ef0199 | |
Matthieu | dafe827a5c | |
Matthieu | 0f8602b3f1 | |
Matthieu | 6edb394ccb | |
Weblate | 13fa45cdb0 | |
Fred | f15c23cceb | |
Fred | d9da842df7 |
|
@ -3,4 +3,4 @@
|
||||||
url = https://gitlab.com/artectrex/scrambler.git
|
url = https://gitlab.com/artectrex/scrambler.git
|
||||||
[submodule "pixel_common"]
|
[submodule "pixel_common"]
|
||||||
path = pixel_common
|
path = pixel_common
|
||||||
url = git@gitlab.shinice.net:pixeldroid/pixel_common.git
|
url = https://gitlab.shinice.net/pixeldroid/pixel_common.git
|
||||||
|
|
|
@ -9,13 +9,16 @@ Free (as in freedom) Android client for Pixelfed, the federated image sharing pl
|
||||||
<img src="https://pixeldroid.org/badge-fdroid.png" alt="Get it on F-Droid" width="206">
|
<img src="https://pixeldroid.org/badge-fdroid.png" alt="Get it on F-Droid" width="206">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
Come talk to us on Matrix, at <a href="https://matrix.to/#/#pixeldroid:gnugen.ch">#pixeldroid:gnugen.ch</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/).
|
||||||
|
|
||||||
## 🎨 Art attribution
|
## 🎨 Art attribution
|
||||||
Our mascot was commissioned using funds from NLnet. The original file is `pixeldroid_mascot.svg` and it is adapted to work as an Android Drawable. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) (CC BY-SA).
|
Our mascot was commissioned using funds from NLnet. The original file is `pixeldroid_mascot.svg` and it is adapted to work as an Android Drawable. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) (CC BY-SA).
|
||||||
|
|
||||||
Various works have been used from the pixelfed branding repository ( https://github.com/pixelfed/brand-assets ). In addition, a drawing of a red panda is used for some error messages ( https://thenounproject.com/search/?q=red+panda&i=2877785 ).
|
Various works have been used from the pixelfed branding repository ( https://github.com/pixelfed/brand-assets ).
|
||||||
|
|
||||||
## 🤝 Contribute
|
## 🤝 Contribute
|
||||||
If you want to contribute, you can check out [CONTRIBUTING.md](CONTRIBUTING.md) and/or [TRANSLATION.md](TRANSLATION.md)
|
If you want to contribute, you can check out [CONTRIBUTING.md](CONTRIBUTING.md) and/or [TRANSLATION.md](TRANSLATION.md)
|
||||||
|
|
||||||
|
|
121
app/build.gradle
121
app/build.gradle
|
@ -1,10 +1,28 @@
|
||||||
import com.android.build.api.dsl.ManagedVirtualDevice
|
import com.android.build.api.dsl.ManagedVirtualDevice
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
apply plugin: 'kotlin-android'
|
id("com.android.application")
|
||||||
apply plugin: 'jacoco'
|
id("com.google.dagger.hilt.android")
|
||||||
apply plugin: "kotlin-parcelize"
|
id("kotlin-android")
|
||||||
apply plugin: 'com.google.devtools.ksp'
|
id("jacoco")
|
||||||
|
id("kotlin-parcelize")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
|
}
|
||||||
|
// Map for the version code that gives each ABI a value.
|
||||||
|
ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, x86: 3, x86_64: 4]
|
||||||
|
|
||||||
|
//Different version codes per architecture (for F-Droid support)
|
||||||
|
android.applicationVariants.configureEach { variant ->
|
||||||
|
variant.outputs.each { output ->
|
||||||
|
def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI))
|
||||||
|
if (baseAbiVersionCode != null) {
|
||||||
|
output.versionCodeOverride = (100 * project.android.defaultConfig.versionCode) + baseAbiVersionCode
|
||||||
|
} else {
|
||||||
|
output.versionCodeOverride = 100 * project.android.defaultConfig.versionCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
|
@ -27,8 +45,8 @@ android {
|
||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
versionCode 26
|
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
|
versionCode 33
|
||||||
versionName "1.0.beta" + versionCode
|
versionName "1.0.beta" + versionCode
|
||||||
|
|
||||||
//TODO add resConfigs("en", "fr", "ja",...) ?
|
//TODO add resConfigs("en", "fr", "ja",...) ?
|
||||||
|
@ -77,6 +95,30 @@ android {
|
||||||
proguardFiles 'proguard-rules.pro'
|
proguardFiles 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splits {
|
||||||
|
// Configures multiple APKs based on ABI.
|
||||||
|
abi {
|
||||||
|
|
||||||
|
// Enables building multiple APKs per ABI.
|
||||||
|
enable true
|
||||||
|
|
||||||
|
// By default all ABIs are included, so use reset() and include to specify that we only
|
||||||
|
// want APKs for "x86", "x86_64", "arm64-v8a" and "armeabi-v7a".
|
||||||
|
|
||||||
|
// Resets the list of ABIs for Gradle to create APKs for to none.
|
||||||
|
reset()
|
||||||
|
|
||||||
|
// Specifies a list of ABIs for Gradle to create APKs for.
|
||||||
|
//noinspection ChromeOsAbiSupport
|
||||||
|
include project.ext.abiCodes.keySet() as String[]
|
||||||
|
|
||||||
|
// Specifies that we don't want to also generate a universal APK that includes all ABIs.
|
||||||
|
universalApk false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a string with the application_id (available in xml etc)
|
* Make a string with the application_id (available in xml etc)
|
||||||
*/
|
*/
|
||||||
|
@ -133,30 +175,30 @@ dependencies {
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
||||||
implementation "androidx.browser:browser:1.7.0"
|
implementation "androidx.browser:browser:1.8.0"
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0'
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.6.2"
|
implementation "androidx.lifecycle:lifecycle-common-java8:2.7.0"
|
||||||
implementation "androidx.annotation:annotation:1.7.1"
|
implementation "androidx.annotation:annotation:1.7.1"
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation "androidx.activity:activity-ktx:1.8.2"
|
implementation "androidx.activity:activity-ktx:1.8.2"
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||||
implementation 'androidx.work:work-runtime-ktx:2.9.0'
|
implementation 'androidx.work:work-runtime-ktx:2.9.0'
|
||||||
implementation 'androidx.media2:media2-widget:1.2.1'
|
implementation 'androidx.media2:media2-widget:1.3.0'
|
||||||
implementation 'androidx.media2:media2-player:1.2.1'
|
implementation 'androidx.media2:media2-player:1.3.0'
|
||||||
|
|
||||||
|
|
||||||
// Use the most recent version of CameraX
|
// Use the most recent version of CameraX
|
||||||
def cameraX_version = '1.3.1'
|
def cameraX_version = '1.3.2'
|
||||||
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
|
||||||
|
@ -181,18 +223,21 @@ dependencies {
|
||||||
implementation 'com.google.android.material:material:1.11.0'
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
|
|
||||||
//Dagger (dependency injection)
|
//Dagger (dependency injection)
|
||||||
implementation 'com.google.dagger:dagger:2.48'
|
implementation 'com.google.dagger:dagger:2.51'
|
||||||
ksp 'com.google.dagger:dagger-compiler:2.48'
|
ksp 'com.google.dagger:dagger-compiler:2.51'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
|
implementation('com.google.dagger:hilt-android:2.51')
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
ksp 'com.google.dagger:hilt-compiler:2.51'
|
||||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
|
||||||
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||||
implementation 'io.reactivex.rxjava3:rxjava:3.1.6'
|
implementation 'com.squareup.retrofit2:retrofit:2.10.0'
|
||||||
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.10.0'
|
||||||
|
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.10.0'
|
||||||
|
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
|
||||||
|
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
|
||||||
implementation 'com.github.connyduck:sparkbutton:4.1.0'
|
implementation 'com.github.connyduck:sparkbutton:4.1.0'
|
||||||
|
|
||||||
implementation 'org.pixeldroid.pixeldroid:android-media-editor:1.5'
|
implementation 'org.pixeldroid.pixeldroid:android-media-editor:2.0'
|
||||||
implementation project(path: ':scrambler')
|
implementation project(path: ':scrambler')
|
||||||
implementation project(path: ':pixel_common')
|
implementation project(path: ':pixel_common')
|
||||||
|
|
||||||
|
@ -200,24 +245,24 @@ dependencies {
|
||||||
exclude group: "com.android.support"
|
exclude group: "com.android.support"
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.github.bumptech.glide:okhttp3-integration:4.14.2'
|
implementation 'com.github.bumptech.glide:okhttp3-integration:4.16.0'
|
||||||
implementation('com.github.bumptech.glide:recyclerview-integration:4.14.2') {
|
implementation('com.github.bumptech.glide:recyclerview-integration:4.16.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
|
||||||
}
|
}
|
||||||
implementation 'com.github.bumptech.glide:annotations:4.16.0'
|
implementation 'com.github.bumptech.glide:annotations:4.16.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||||
ksp 'com.github.bumptech.glide:ksp:4.14.2'
|
ksp 'com.github.bumptech.glide:ksp:4.16.0'
|
||||||
|
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
||||||
implementation 'com.mikepenz:materialdrawer:9.0.1'
|
implementation 'com.mikepenz:materialdrawer:9.0.2'
|
||||||
// Add for NavController support
|
// Add for NavController support
|
||||||
implementation 'com.mikepenz:materialdrawer-nav:9.0.1'
|
implementation 'com.mikepenz:materialdrawer-nav:9.0.2'
|
||||||
|
|
||||||
//iconics
|
//iconics
|
||||||
implementation 'com.mikepenz:iconics-core:5.4.0'
|
implementation 'com.mikepenz:iconics-core:5.4.0'
|
||||||
implementation 'com.mikepenz:materialdrawer-iconics:9.0.1'
|
implementation 'com.mikepenz:materialdrawer-iconics:9.0.2'
|
||||||
implementation 'com.mikepenz:iconics-views:5.4.0'
|
implementation 'com.mikepenz:iconics-views:5.4.0'
|
||||||
implementation 'com.mikepenz:google-material-typeface:4.0.0.2-kotlin@aar'
|
implementation 'com.mikepenz:google-material-typeface:4.0.0.2-kotlin@aar'
|
||||||
|
|
||||||
|
@ -242,7 +287,7 @@ dependencies {
|
||||||
testImplementation "androidx.room:room-testing:$room_version"
|
testImplementation "androidx.room:room-testing:$room_version"
|
||||||
|
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
@ -251,10 +296,12 @@ dependencies {
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
|
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
|
||||||
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.9.2'
|
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
tasks.withType(Test).configureEach {
|
tasks.withType(Test).configureEach {
|
||||||
jacoco.includeNoLocationClasses = true
|
jacoco.includeNoLocationClasses = true
|
||||||
jacoco.excludes = ['jdk.internal.*']
|
jacoco.excludes = ['jdk.internal.*']
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".posts.AlbumActivity"
|
android:name=".posts.AlbumActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/AppTheme.ActionBar.Transparent"/>
|
android:theme="@style/TransparentAlbumActivity"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".profile.EditProfileActivity"
|
android:name=".profile.EditProfileActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -29,6 +30,7 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
|
@ -36,7 +38,12 @@ import com.mikepenz.materialdrawer.iconics.iconicsIcon
|
||||||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
|
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
|
||||||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
||||||
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
|
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
|
||||||
import com.mikepenz.materialdrawer.model.interfaces.*
|
import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
||||||
|
import com.mikepenz.materialdrawer.model.interfaces.descriptionRes
|
||||||
|
import com.mikepenz.materialdrawer.model.interfaces.descriptionText
|
||||||
|
import com.mikepenz.materialdrawer.model.interfaces.iconUrl
|
||||||
|
import com.mikepenz.materialdrawer.model.interfaces.nameRes
|
||||||
|
import com.mikepenz.materialdrawer.model.interfaces.nameText
|
||||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
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
|
||||||
|
@ -53,10 +60,10 @@ import org.pixeldroid.app.searchDiscover.SearchDiscoverFragment
|
||||||
import org.pixeldroid.app.settings.SettingsActivity
|
import org.pixeldroid.app.settings.SettingsActivity
|
||||||
import org.pixeldroid.app.utils.BaseActivity
|
import org.pixeldroid.app.utils.BaseActivity
|
||||||
import org.pixeldroid.app.utils.api.objects.Notification
|
import org.pixeldroid.app.utils.api.objects.Notification
|
||||||
import org.pixeldroid.app.utils.db.addUser
|
|
||||||
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
|
||||||
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
|
||||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||||
|
import org.pixeldroid.app.utils.db.updateUserInfoDb
|
||||||
import org.pixeldroid.app.utils.hasInternet
|
import org.pixeldroid.app.utils.hasInternet
|
||||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.INSTANCE_NOTIFICATION_TAG
|
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.INSTANCE_NOTIFICATION_TAG
|
||||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_NOTIFICATION_TAG
|
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_NOTIFICATION_TAG
|
||||||
|
@ -71,6 +78,8 @@ class MainActivity : BaseActivity() {
|
||||||
private lateinit var header: AccountHeaderView
|
private lateinit var header: AccountHeaderView
|
||||||
private var user: UserDatabaseEntity? = null
|
private var user: UserDatabaseEntity? = null
|
||||||
|
|
||||||
|
private val model: MainActivityViewModel by viewModels()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ADD_ACCOUNT_IDENTIFIER: Long = -13
|
const val ADD_ACCOUNT_IDENTIFIER: Long = -13
|
||||||
}
|
}
|
||||||
|
@ -196,6 +205,7 @@ class MainActivity : BaseActivity() {
|
||||||
Glide.with(this@MainActivity)
|
Glide.with(this@MainActivity)
|
||||||
.load(uri)
|
.load(uri)
|
||||||
.placeholder(placeholder)
|
.placeholder(placeholder)
|
||||||
|
.circleCrop()
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,13 +274,13 @@ class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
val remainingUsers = db.userDao().getAll()
|
val remainingUsers = db.userDao().getAll()
|
||||||
if (remainingUsers.isEmpty()){
|
if (remainingUsers.isEmpty()){
|
||||||
//no more users, start first-time login flow
|
// No more users, start first-time login flow
|
||||||
launchActivity(LoginActivity(), firstTime = true)
|
launchActivity(LoginActivity(), firstTime = true)
|
||||||
} else {
|
} else {
|
||||||
val newActive = remainingUsers.first()
|
val newActive = remainingUsers.first()
|
||||||
db.userDao().activateUser(newActive.user_id, newActive.instance_uri)
|
db.userDao().activateUser(newActive.user_id, newActive.instance_uri)
|
||||||
apiHolder.setToCurrentUser()
|
apiHolder.setToCurrentUser()
|
||||||
//relaunch the app
|
// Relaunch the app
|
||||||
launchActivity(MainActivity(), firstTime = true)
|
launchActivity(MainActivity(), firstTime = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,16 +291,12 @@ class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
try {
|
try {
|
||||||
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 api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||||
|
|
||||||
val account = api.verifyCredentials()
|
val account = api.verifyCredentials()
|
||||||
addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
|
updateUserInfoDb(db, account)
|
||||||
fillDrawerAccountInfo(account.id!!)
|
|
||||||
|
//No need to update drawer account info here, the ViewModel listens to db updates
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.e("ACCOUNT UPDATE:", exception.toString())
|
Log.e("ACCOUNT UPDATE:", exception.toString())
|
||||||
}
|
}
|
||||||
|
@ -322,9 +328,11 @@ class MainActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun switchUser(userId: String, instance_uri: String) {
|
private fun switchUser(userId: String, instance_uri: String) {
|
||||||
db.userDao().deActivateActiveUsers()
|
db.runInTransaction{
|
||||||
db.userDao().activateUser(userId, instance_uri)
|
db.userDao().deActivateActiveUsers()
|
||||||
apiHolder.setToCurrentUser()
|
db.userDao().activateUser(userId, instance_uri)
|
||||||
|
apiHolder.setToCurrentUser()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem {
|
private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem {
|
||||||
|
@ -337,35 +345,41 @@ class MainActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillDrawerAccountInfo(account: String) {
|
private fun fillDrawerAccountInfo(account: String) {
|
||||||
val users = db.userDao().getAll().toMutableList()
|
lifecycleScope.launch {
|
||||||
users.sortWith { l, r ->
|
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
when {
|
model.users.collect { list ->
|
||||||
l.isActive && !r.isActive -> -1
|
val users = list.toMutableList()
|
||||||
r.isActive && !l.isActive -> 1
|
users.sortWith { l, r ->
|
||||||
else -> 0
|
when {
|
||||||
|
l.isActive && !r.isActive -> -1
|
||||||
|
r.isActive && !l.isActive -> 1
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val profiles: MutableList<IProfile> = users.map { user ->
|
||||||
|
ProfileDrawerItem().apply {
|
||||||
|
isSelected = user.isActive
|
||||||
|
nameText = user.display_name
|
||||||
|
iconUrl = user.avatar_static
|
||||||
|
isNameShown = true
|
||||||
|
identifier = user.user_id.toLong()
|
||||||
|
descriptionText = user.fullHandle
|
||||||
|
tag = user.instance_uri
|
||||||
|
}
|
||||||
|
}.toMutableList()
|
||||||
|
|
||||||
|
// reuse the already existing "add account" item
|
||||||
|
header.profiles.orEmpty()
|
||||||
|
.filter { it.identifier == ADD_ACCOUNT_IDENTIFIER }
|
||||||
|
.take(1)
|
||||||
|
.forEach { profiles.add(it) }
|
||||||
|
|
||||||
|
header.clear()
|
||||||
|
header.profiles = profiles
|
||||||
|
header.setActiveProfile(account.toLong())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val profiles: MutableList<IProfile> = users.map { user ->
|
|
||||||
ProfileDrawerItem().apply {
|
|
||||||
isSelected = user.isActive
|
|
||||||
nameText = user.display_name
|
|
||||||
iconUrl = user.avatar_static
|
|
||||||
isNameShown = true
|
|
||||||
identifier = user.user_id.toLong()
|
|
||||||
descriptionText = user.fullHandle
|
|
||||||
tag = user.instance_uri
|
|
||||||
}
|
|
||||||
}.toMutableList()
|
|
||||||
|
|
||||||
// reuse the already existing "add account" item
|
|
||||||
header.profiles.orEmpty()
|
|
||||||
.filter { it.identifier == ADD_ACCOUNT_IDENTIFIER }
|
|
||||||
.take(1)
|
|
||||||
.forEach { profiles.add(it) }
|
|
||||||
|
|
||||||
header.clear()
|
|
||||||
header.profiles = profiles
|
|
||||||
header.setActiveProfile(account.toLong())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.pixeldroid.app
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
|
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class MainActivityViewModel @Inject constructor(
|
||||||
|
private val db: AppDatabase
|
||||||
|
): ViewModel() {
|
||||||
|
|
||||||
|
// Mutable state flow that will be used internally in the ViewModel, empty list is given as initial value.
|
||||||
|
private val _users = MutableStateFlow(emptyList<UserDatabaseEntity>())
|
||||||
|
|
||||||
|
// Immutable state flow exposed to UI
|
||||||
|
val users = _users.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
getUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUsers() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
db.userDao().getAllFlow().flowOn(Dispatchers.IO)
|
||||||
|
.collect { users: List<UserDatabaseEntity> ->
|
||||||
|
_users.update { users }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +1,57 @@
|
||||||
package org.pixeldroid.app.postCreation
|
package org.pixeldroid.app.postCreation
|
||||||
|
|
||||||
import android.os.*
|
import android.content.ClipData
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.databinding.ActivityPostCreationBinding
|
import org.pixeldroid.app.databinding.ActivityPostCreationBinding
|
||||||
import org.pixeldroid.app.utils.BaseActivity
|
import org.pixeldroid.app.utils.BaseActivity
|
||||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
|
|
||||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
|
||||||
|
|
||||||
const val TAG = "Post Creation Activity"
|
|
||||||
|
|
||||||
class PostCreationActivity : BaseActivity() {
|
class PostCreationActivity : BaseActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal const val PICTURE_DESCRIPTION = "picture_description"
|
internal const val POST_DESCRIPTION = "post_description"
|
||||||
|
internal const val PICTURE_DESCRIPTIONS = "picture_descriptions"
|
||||||
internal const val POST_REDRAFT = "post_redraft"
|
internal const val POST_REDRAFT = "post_redraft"
|
||||||
internal const val POST_NSFW = "post_nsfw"
|
internal const val POST_NSFW = "post_nsfw"
|
||||||
internal const val TEMP_FILES = "temp_files"
|
internal const val TEMP_FILES = "temp_files"
|
||||||
|
|
||||||
|
fun intentForUris(context: Context, uris: List<Uri>) =
|
||||||
|
Intent(Intent.ACTION_SEND_MULTIPLE).apply {
|
||||||
|
// Pass downloaded images to new post creation activity
|
||||||
|
putParcelableArrayListExtra(
|
||||||
|
Intent.EXTRA_STREAM, ArrayList(uris)
|
||||||
|
)
|
||||||
|
|
||||||
|
uris.forEach {
|
||||||
|
// Why are we using ClipData in addition to parcelableArrayListExtra here?
|
||||||
|
// Because the FLAG_GRANT_READ_URI_PERMISSION needs to be applied to the URIs, and
|
||||||
|
// for some reason it doesn't get applied to all of them when not using ClipData
|
||||||
|
if (clipData == null) {
|
||||||
|
clipData = ClipData("", emptyArray(), ClipData.Item(it))
|
||||||
|
} else {
|
||||||
|
clipData!!.addItem(ClipData.Item(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setClass(context, PostCreationActivity::class.java)
|
||||||
|
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var user: UserDatabaseEntity? = null
|
|
||||||
private lateinit var instance: InstanceDatabaseEntity
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityPostCreationBinding
|
private lateinit var binding: ActivityPostCreationBinding
|
||||||
|
|
||||||
private lateinit var navController: NavController
|
private lateinit var navController: NavController
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
user = db.userDao().getActiveUser()
|
|
||||||
|
|
||||||
instance = user?.run {
|
|
||||||
db.instanceDao().getAll().first { instanceDatabaseEntity ->
|
|
||||||
instanceDatabaseEntity.uri.contains(instance_uri)
|
|
||||||
}
|
|
||||||
} ?: InstanceDatabaseEntity("", "")
|
|
||||||
|
|
||||||
binding = ActivityPostCreationBinding.inflate(layoutInflater)
|
binding = ActivityPostCreationBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
|
@ -46,8 +60,6 @@ class PostCreationActivity : BaseActivity() {
|
||||||
navController.setGraph(R.navigation.post_creation_graph)
|
navController.setGraph(R.navigation.post_creation_graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp() = navController.navigateUp() || super.onSupportNavigateUp()
|
||||||
return navController.navigateUp() || super.onSupportNavigateUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -33,29 +33,24 @@ import kotlinx.coroutines.launch
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.databinding.FragmentPostCreationBinding
|
import org.pixeldroid.app.databinding.FragmentPostCreationBinding
|
||||||
import org.pixeldroid.app.postCreation.camera.CameraActivity
|
import org.pixeldroid.app.postCreation.camera.CameraActivity
|
||||||
import org.pixeldroid.app.postCreation.camera.CameraFragment
|
|
||||||
import org.pixeldroid.app.postCreation.carousel.CarouselItem
|
import org.pixeldroid.app.postCreation.carousel.CarouselItem
|
||||||
import org.pixeldroid.app.utils.BaseFragment
|
import org.pixeldroid.app.utils.BaseFragment
|
||||||
import org.pixeldroid.app.utils.bindingLifecycleAware
|
import org.pixeldroid.app.utils.bindingLifecycleAware
|
||||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
|
||||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
|
||||||
import org.pixeldroid.app.utils.fileExtension
|
import org.pixeldroid.app.utils.fileExtension
|
||||||
import org.pixeldroid.app.utils.getMimeType
|
import org.pixeldroid.app.utils.getMimeType
|
||||||
|
import org.pixeldroid.media_editor.common.PICTURE_POSITION
|
||||||
|
import org.pixeldroid.media_editor.common.PICTURE_URI
|
||||||
import org.pixeldroid.media_editor.photoEdit.PhotoEditActivity
|
import org.pixeldroid.media_editor.photoEdit.PhotoEditActivity
|
||||||
import org.pixeldroid.media_editor.photoEdit.VideoEditActivity
|
import org.pixeldroid.media_editor.videoEdit.VideoEditActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
class PostCreationFragment : BaseFragment() {
|
class PostCreationFragment : BaseFragment() {
|
||||||
|
|
||||||
private var user: UserDatabaseEntity? = null
|
|
||||||
private var instance: InstanceDatabaseEntity = InstanceDatabaseEntity("", "")
|
|
||||||
|
|
||||||
private var binding: FragmentPostCreationBinding by bindingLifecycleAware()
|
private var binding: FragmentPostCreationBinding by bindingLifecycleAware()
|
||||||
private lateinit var model: PostCreationViewModel
|
private val model: PostCreationViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
@ -72,30 +67,16 @@ class PostCreationFragment : BaseFragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
user = db.userDao().getActiveUser()
|
val user = db.userDao().getActiveUser()
|
||||||
|
|
||||||
instance = user?.run {
|
val instance = user?.run {
|
||||||
db.instanceDao().getAll().first { instanceDatabaseEntity ->
|
db.instanceDao().getInstance(instance_uri)
|
||||||
instanceDatabaseEntity.uri.contains(instance_uri)
|
|
||||||
}
|
|
||||||
} ?: InstanceDatabaseEntity("", "")
|
} ?: InstanceDatabaseEntity("", "")
|
||||||
|
|
||||||
val _model: PostCreationViewModel by activityViewModels {
|
model.getPhotoData().observe(viewLifecycleOwner) { newPhotoData: MutableList<PhotoData>? ->
|
||||||
PostCreationViewModelFactory(
|
|
||||||
requireActivity().application,
|
|
||||||
requireActivity().intent.clipData!!,
|
|
||||||
instance,
|
|
||||||
requireActivity().intent.getStringExtra(PostCreationActivity.PICTURE_DESCRIPTION),
|
|
||||||
requireActivity().intent.getBooleanExtra(PostCreationActivity.POST_NSFW, false),
|
|
||||||
requireActivity().intent.getBooleanExtra(CameraFragment.CAMERA_ACTIVITY_STORY, false),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
model = _model
|
|
||||||
|
|
||||||
model.getPhotoData().observe(viewLifecycleOwner) { newPhotoData ->
|
|
||||||
// update UI
|
// update UI
|
||||||
binding.carousel.addData(
|
binding.carousel.addData(
|
||||||
newPhotoData.map {
|
newPhotoData.orEmpty().map {
|
||||||
CarouselItem(
|
CarouselItem(
|
||||||
it.imageUri, it.imageDescription, it.video,
|
it.imageUri, it.imageDescription, it.video,
|
||||||
it.videoEncodeProgress, it.videoEncodeStabilizationFirstPass,
|
it.videoEncodeProgress, it.videoEncodeStabilizationFirstPass,
|
||||||
|
@ -103,7 +84,7 @@ class PostCreationFragment : BaseFragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
binding.postCreationNextButton.isEnabled = newPhotoData.isNotEmpty()
|
binding.postCreationNextButton.isEnabled = newPhotoData?.isNotEmpty() ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
@ -227,10 +208,9 @@ class PostCreationFragment : BaseFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == Activity.RESULT_OK && result.data?.clipData != null) {
|
val uris = result.data?.extras?.getParcelableArrayList<Uri>(Intent.EXTRA_STREAM)
|
||||||
result.data?.clipData?.let {
|
if (result.resultCode == Activity.RESULT_OK && uris != null) {
|
||||||
model.setImages(model.addPossibleImages(it))
|
model.setImages(model.addPossibleImages(uris, emptyList()))
|
||||||
}
|
|
||||||
} else if (result.resultCode != Activity.RESULT_CANCELED) {
|
} else if (result.resultCode != Activity.RESULT_CANCELED) {
|
||||||
Toast.makeText(requireActivity(), R.string.add_images_error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireActivity(), R.string.add_images_error, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
@ -328,7 +308,7 @@ class PostCreationFragment : BaseFragment() {
|
||||||
ActivityResultContracts.StartActivityForResult()){
|
ActivityResultContracts.StartActivityForResult()){
|
||||||
result: ActivityResult? ->
|
result: ActivityResult? ->
|
||||||
if (result?.resultCode == Activity.RESULT_OK && result.data != null) {
|
if (result?.resultCode == Activity.RESULT_OK && result.data != null) {
|
||||||
val position: Int = result.data!!.getIntExtra(PhotoEditActivity.PICTURE_POSITION, 0)
|
val position: Int = result.data!!.getIntExtra(PICTURE_POSITION, 0)
|
||||||
model.modifyAt(position, result.data!!)
|
model.modifyAt(position, result.data!!)
|
||||||
?: Toast.makeText(requireActivity(), R.string.error_editing, Toast.LENGTH_SHORT).show()
|
?: Toast.makeText(requireActivity(), R.string.error_editing, Toast.LENGTH_SHORT).show()
|
||||||
} else if(result?.resultCode != Activity.RESULT_CANCELED){
|
} else if(result?.resultCode != Activity.RESULT_CANCELED){
|
||||||
|
@ -341,8 +321,8 @@ class PostCreationFragment : BaseFragment() {
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
if (model.getPhotoData().value!![position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java
|
if (model.getPhotoData().value!![position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java
|
||||||
)
|
)
|
||||||
.putExtra(PhotoEditActivity.PICTURE_URI, model.getPhotoData().value!![position].imageUri)
|
.putExtra(PICTURE_URI, model.getPhotoData().value!![position].imageUri)
|
||||||
.putExtra(PhotoEditActivity.PICTURE_POSITION, position)
|
.putExtra(PICTURE_POSITION, position)
|
||||||
|
|
||||||
editResultContract.launch(intent)
|
editResultContract.launch(intent)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.pixeldroid.app.postCreation
|
package org.pixeldroid.app.postCreation
|
||||||
|
|
||||||
import android.app.Application
|
import android.content.Context
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -12,15 +11,16 @@ import android.widget.Toast
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.jarsilio.android.scrambler.exceptions.UnsupportedFileFormatException
|
import com.jarsilio.android.scrambler.exceptions.UnsupportedFileFormatException
|
||||||
import com.jarsilio.android.scrambler.stripMetadata
|
import com.jarsilio.android.scrambler.stripMetadata
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
@ -33,35 +33,25 @@ import kotlinx.parcelize.Parcelize
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import org.pixeldroid.app.MainActivity
|
import org.pixeldroid.app.MainActivity
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
import org.pixeldroid.app.postCreation.camera.CameraFragment
|
||||||
import org.pixeldroid.app.utils.api.objects.Attachment
|
import org.pixeldroid.app.utils.api.objects.Attachment
|
||||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||||
import org.pixeldroid.app.utils.fileExtension
|
import org.pixeldroid.app.utils.fileExtension
|
||||||
import org.pixeldroid.app.utils.getMimeType
|
import org.pixeldroid.app.utils.getMimeType
|
||||||
import org.pixeldroid.media_editor.photoEdit.VideoEditActivity
|
import org.pixeldroid.media_editor.common.PICTURE_URI
|
||||||
|
import org.pixeldroid.media_editor.videoEdit.VideoEditActivity
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.collections.MutableList
|
|
||||||
import kotlin.collections.MutableMap
|
|
||||||
import kotlin.collections.arrayListOf
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
import kotlin.collections.get
|
|
||||||
import kotlin.collections.getOrNull
|
|
||||||
import kotlin.collections.indexOfFirst
|
|
||||||
import kotlin.collections.mutableListOf
|
|
||||||
import kotlin.collections.mutableMapOf
|
|
||||||
import kotlin.collections.plus
|
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.collections.toMutableList
|
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
const val TAG = "Post Creation ViewModel"
|
||||||
|
|
||||||
// Models the UI state for the PostCreationActivity
|
// Models the UI state for the PostCreationActivity
|
||||||
data class PostCreationActivityUiState(
|
data class PostCreationActivityUiState(
|
||||||
|
@ -108,35 +98,56 @@ data class PhotoData(
|
||||||
var videoEncodeError: Boolean = false,
|
var videoEncodeError: Boolean = false,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
class PostCreationViewModel(
|
@HiltViewModel
|
||||||
application: Application,
|
class PostCreationViewModel @Inject constructor(
|
||||||
clipdata: ClipData? = null,
|
private val state: SavedStateHandle,
|
||||||
val instance: InstanceDatabaseEntity? = null,
|
@ApplicationContext private val applicationContext: Context,
|
||||||
existingDescription: String? = null,
|
db: AppDatabase,
|
||||||
existingNSFW: Boolean = false,
|
): ViewModel() {
|
||||||
storyCreation: Boolean = false,
|
|
||||||
) : AndroidViewModel(application) {
|
|
||||||
private var storyPhotoDataBackup: MutableList<PhotoData>? = null
|
private var storyPhotoDataBackup: MutableList<PhotoData>? = null
|
||||||
private val photoData: MutableLiveData<MutableList<PhotoData>> by lazy {
|
private val photoData: MutableLiveData<MutableList<PhotoData>> by lazy {
|
||||||
MutableLiveData<MutableList<PhotoData>>().also {
|
//FIXME We should be able to access the Intent action somehow, to determine if there are
|
||||||
it.value = clipdata?.let { it1 -> addPossibleImages(it1, mutableListOf()) }
|
// 1 or multiple Uris instead of relying on the ClassCastException
|
||||||
|
|
||||||
|
// This should not work like this (reading its source code, get() function should return null
|
||||||
|
// if it's the wrong type but instead throws ClassCastException).
|
||||||
|
// Lucky for us that it does though: we first try to get a single Uri (which we could be
|
||||||
|
// getting from a share of a single picture to the app), when the cast to Uri fails
|
||||||
|
// we try to get a list of Uris instead (casting ourselves from Parcelable as suggested
|
||||||
|
// in get() documentation)
|
||||||
|
val uris = try {
|
||||||
|
val singleUri: Uri? = state[Intent.EXTRA_STREAM]
|
||||||
|
listOfNotNull(singleUri)
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
state.get<ArrayList<Parcelable>>(Intent.EXTRA_STREAM)?.map { it as Uri }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MutableLiveData<MutableList<PhotoData>>(
|
||||||
|
addPossibleImages(
|
||||||
|
uris,
|
||||||
|
state.get<ArrayList<String>>(PostCreationActivity.PICTURE_DESCRIPTIONS),
|
||||||
|
previousList = mutableListOf()
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val instance = db.instanceDao().getActiveInstance()
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var apiHolder: PixelfedAPIHolder
|
lateinit var apiHolder: PixelfedAPIHolder
|
||||||
|
|
||||||
private val _uiState: MutableStateFlow<PostCreationActivityUiState>
|
private val _uiState: MutableStateFlow<PostCreationActivityUiState>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
(application as PixelDroidApplication).getAppComponent().inject(this)
|
|
||||||
val sharedPreferences =
|
val sharedPreferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(application)
|
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
val templateDescription = sharedPreferences.getString("prefill_description", "") ?: ""
|
val templateDescription = sharedPreferences.getString("prefill_description", "") ?: ""
|
||||||
|
|
||||||
|
val storyCreation: Boolean = state[CameraFragment.CAMERA_ACTIVITY_STORY] ?: false
|
||||||
|
|
||||||
_uiState = MutableStateFlow(PostCreationActivityUiState(
|
_uiState = MutableStateFlow(PostCreationActivityUiState(
|
||||||
newPostDescriptionText = existingDescription ?: templateDescription,
|
newPostDescriptionText = state[PostCreationActivity.POST_DESCRIPTION] ?: templateDescription,
|
||||||
nsfw = existingNSFW,
|
nsfw = state[PostCreationActivity.POST_NSFW] ?: false,
|
||||||
maxEntries = if(storyCreation) 1 else instance?.albumLimit,
|
maxEntries = if(storyCreation) 1 else instance?.albumLimit,
|
||||||
storyCreation = storyCreation
|
storyCreation = storyCreation
|
||||||
))
|
))
|
||||||
|
@ -161,32 +172,41 @@ class PostCreationViewModel(
|
||||||
fun getPhotoData(): LiveData<MutableList<PhotoData>> = photoData
|
fun getPhotoData(): LiveData<MutableList<PhotoData>> = photoData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will add as many images as possible to [photoData], from the [clipData], and if
|
* Will add as many images as possible to [photoData], from the [uris], and if
|
||||||
* ([photoData].size + [clipData].itemCount) > uiState.value.maxEntries then it will only add as many images
|
* ([photoData].size + [uris].size) > uiState.value.maxEntries then it will only add as many images
|
||||||
* as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
|
* as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
|
||||||
*/
|
*/
|
||||||
fun addPossibleImages(clipData: ClipData, previousList: MutableList<PhotoData>? = photoData.value): MutableList<PhotoData> {
|
fun addPossibleImages(
|
||||||
|
uris: List<Uri>?,
|
||||||
|
descriptions: List<String>?,
|
||||||
|
previousList: MutableList<PhotoData>? = photoData.value,
|
||||||
|
): MutableList<PhotoData> {
|
||||||
val dataToAdd: ArrayList<PhotoData> = arrayListOf()
|
val dataToAdd: ArrayList<PhotoData> = arrayListOf()
|
||||||
var count = clipData.itemCount
|
var count = uris?.size ?: 0
|
||||||
uiState.value.maxEntries?.let {
|
uiState.value.maxEntries?.let { maxEntries ->
|
||||||
if(count + (previousList?.size ?: 0) > it){
|
if(count + (previousList?.size ?: 0) > maxEntries){
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(userMessage = getApplication<PixelDroidApplication>().getString(R.string.total_exceeds_album_limit).format(it))
|
currentUiState.copy(userMessage = applicationContext.getString(R.string.total_exceeds_album_limit).format(maxEntries))
|
||||||
}
|
}
|
||||||
count = count.coerceAtMost(it - (previousList?.size ?: 0))
|
count = count.coerceAtMost(maxEntries - (previousList?.size ?: 0))
|
||||||
}
|
}
|
||||||
if (count + (previousList?.size ?: 0) >= it) {
|
if (count + (previousList?.size ?: 0) >= maxEntries) {
|
||||||
// Disable buttons to add more images
|
// Disable buttons to add more images
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(addPhotoButtonEnabled = false)
|
currentUiState.copy(addPhotoButtonEnabled = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i in 0 until count) {
|
for ((i, uri) in uris.orEmpty().withIndex()) {
|
||||||
clipData.getItemAt(i).let {
|
val sizeAndVideoPair: Pair<Long, Boolean> =
|
||||||
val sizeAndVideoPair: Pair<Long, Boolean> =
|
getSizeAndVideoValidate(uri, (previousList?.size ?: 0) + dataToAdd.size + 1)
|
||||||
getSizeAndVideoValidate(it.uri, (previousList?.size ?: 0) + dataToAdd.size + 1)
|
dataToAdd.add(
|
||||||
dataToAdd.add(PhotoData(imageUri = it.uri, size = sizeAndVideoPair.first, video = sizeAndVideoPair.second, imageDescription = it.text?.toString()))
|
PhotoData(
|
||||||
}
|
imageUri = uri,
|
||||||
|
size = sizeAndVideoPair.first,
|
||||||
|
video = sizeAndVideoPair.second,
|
||||||
|
imageDescription = descriptions?.getOrNull(i)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +224,7 @@ class PostCreationViewModel(
|
||||||
private fun getSizeAndVideoValidate(uri: Uri, editPosition: Int): Pair<Long, Boolean> {
|
private fun getSizeAndVideoValidate(uri: Uri, editPosition: Int): Pair<Long, Boolean> {
|
||||||
val size: Long =
|
val size: Long =
|
||||||
if (uri.scheme =="content") {
|
if (uri.scheme =="content") {
|
||||||
getApplication<PixelDroidApplication>().contentResolver.query(uri, null, null, null, null)
|
applicationContext.contentResolver.query(uri, null, null, null, null)
|
||||||
?.use { cursor ->
|
?.use { cursor ->
|
||||||
/* Get the column indexes of the data in the Cursor,
|
/* Get the column indexes of the data in the Cursor,
|
||||||
* move to the first row in the Cursor, get the data,
|
* move to the first row in the Cursor, get the data,
|
||||||
|
@ -221,12 +241,12 @@ class PostCreationViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
|
val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
|
||||||
val type = uri.getMimeType(getApplication<PixelDroidApplication>().contentResolver)
|
val type = uri.getMimeType(applicationContext.contentResolver)
|
||||||
val isVideo = type.startsWith("video/")
|
val isVideo = type.startsWith("video/")
|
||||||
|
|
||||||
if (isVideo && !instance!!.videoEnabled) {
|
if (isVideo && !instance!!.videoEnabled) {
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(userMessage = getApplication<PixelDroidApplication>().getString(R.string.video_not_supported))
|
currentUiState.copy(userMessage = applicationContext.getString(R.string.video_not_supported))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +255,7 @@ class PostCreationViewModel(
|
||||||
val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize
|
val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
userMessage = getApplication<PixelDroidApplication>().getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize)
|
userMessage = applicationContext.getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,7 +292,7 @@ class PostCreationViewModel(
|
||||||
videoEncodeComplete = false
|
videoEncodeComplete = false
|
||||||
|
|
||||||
VideoEditActivity.startEncoding(imageUri, null, it,
|
VideoEditActivity.startEncoding(imageUri, null, it,
|
||||||
context = getApplication<PixelDroidApplication>(),
|
context = applicationContext,
|
||||||
registerNewFFmpegSession = ::registerNewFFmpegSession,
|
registerNewFFmpegSession = ::registerNewFFmpegSession,
|
||||||
trackTempFile = ::trackTempFile,
|
trackTempFile = ::trackTempFile,
|
||||||
videoEncodeProgress = ::videoEncodeProgress
|
videoEncodeProgress = ::videoEncodeProgress
|
||||||
|
@ -280,7 +300,7 @@ class PostCreationViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
imageUri = data.getStringExtra(org.pixeldroid.media_editor.photoEdit.PhotoEditActivity.PICTURE_URI)!!.toUri()
|
imageUri = data.getStringExtra(PICTURE_URI)!!.toUri()
|
||||||
val (imageSize, imageVideo) = getSizeAndVideoValidate(imageUri, position)
|
val (imageSize, imageVideo) = getSizeAndVideoValidate(imageUri, position)
|
||||||
size = imageSize
|
size = imageSize
|
||||||
video = imageVideo
|
video = imageVideo
|
||||||
|
@ -387,17 +407,17 @@ class PostCreationViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
for (data: PhotoData in getPhotoData().value ?: emptyList()) {
|
for (data: PhotoData in getPhotoData().value ?: emptyList()) {
|
||||||
val extension = data.imageUri.fileExtension(getApplication<PixelDroidApplication>().contentResolver)
|
val extension = data.imageUri.fileExtension(applicationContext.contentResolver)
|
||||||
|
|
||||||
val strippedImage = File.createTempFile("temp_img", ".$extension", getApplication<PixelDroidApplication>().cacheDir)
|
val strippedImage = File.createTempFile("temp_img", ".$extension", applicationContext.cacheDir)
|
||||||
|
|
||||||
val imageUri = data.imageUri
|
val imageUri = data.imageUri
|
||||||
|
|
||||||
val (strippedOrNot, size) = try {
|
val (strippedOrNot, size) = try {
|
||||||
val orientation = ExifInterface(getApplication<PixelDroidApplication>().contentResolver.openInputStream(imageUri)!!).getAttributeInt(
|
val orientation = ExifInterface(applicationContext.contentResolver.openInputStream(imageUri)!!).getAttributeInt(
|
||||||
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
||||||
|
|
||||||
stripMetadata(imageUri, strippedImage, getApplication<PixelDroidApplication>().contentResolver)
|
stripMetadata(imageUri, strippedImage, applicationContext.contentResolver)
|
||||||
|
|
||||||
// Restore EXIF orientation
|
// Restore EXIF orientation
|
||||||
val exifInterface = ExifInterface(strippedImage)
|
val exifInterface = ExifInterface(strippedImage)
|
||||||
|
@ -409,11 +429,11 @@ class PostCreationViewModel(
|
||||||
strippedImage.delete()
|
strippedImage.delete()
|
||||||
if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete()
|
if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete()
|
||||||
val imageInputStream = try {
|
val imageInputStream = try {
|
||||||
getApplication<PixelDroidApplication>().contentResolver.openInputStream(imageUri)!!
|
applicationContext.contentResolver.openInputStream(imageUri)!!
|
||||||
} catch (e: FileNotFoundException){
|
} catch (e: FileNotFoundException){
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
userMessage = getApplication<PixelDroidApplication>().getString(R.string.file_not_found,
|
userMessage = applicationContext.getString(R.string.file_not_found,
|
||||||
data.imageUri)
|
data.imageUri)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -425,14 +445,14 @@ class PostCreationViewModel(
|
||||||
if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete()
|
if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete()
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
userMessage = getApplication<PixelDroidApplication>().getString(R.string.file_not_found,
|
userMessage = applicationContext.getString(R.string.file_not_found,
|
||||||
data.imageUri)
|
data.imageUri)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val type = data.imageUri.getMimeType(getApplication<PixelDroidApplication>().contentResolver)
|
val type = data.imageUri.getMimeType(applicationContext.contentResolver)
|
||||||
val imagePart = ProgressRequestBody(strippedOrNot, size, type)
|
val imagePart = ProgressRequestBody(strippedOrNot, size, type)
|
||||||
val requestBody = MultipartBody.Builder()
|
val requestBody = MultipartBody.Builder()
|
||||||
.setType(MultipartBody.FORM)
|
.setType(MultipartBody.FORM)
|
||||||
|
@ -482,7 +502,7 @@ class PostCreationViewModel(
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
uploadErrorVisible = true,
|
uploadErrorVisible = true,
|
||||||
uploadErrorExplanationText = if(e is HttpException){
|
uploadErrorExplanationText = if(e is HttpException){
|
||||||
getApplication<PixelDroidApplication>().getString(R.string.upload_error, e.code())
|
applicationContext.getString(R.string.upload_error, e.code())
|
||||||
} else "",
|
} else "",
|
||||||
uploadErrorExplanationVisible = e is HttpException,
|
uploadErrorExplanationVisible = e is HttpException,
|
||||||
)
|
)
|
||||||
|
@ -548,14 +568,14 @@ class PostCreationViewModel(
|
||||||
sensitive = nsfw
|
sensitive = nsfw
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Toast.makeText(getApplication(), getApplication<PixelDroidApplication>().getString(R.string.upload_post_success),
|
Toast.makeText(applicationContext, applicationContext.getString(R.string.upload_post_success),
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
val intent = Intent(getApplication(), MainActivity::class.java)
|
val intent = Intent(applicationContext, 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
|
||||||
//TODO make the activity launch this instead (and surrounding toasts too)
|
//TODO make the activity launch this instead (and surrounding toasts too)
|
||||||
getApplication<PixelDroidApplication>().startActivity(intent)
|
applicationContext.startActivity(intent)
|
||||||
} catch (exception: IOException) {
|
} catch (exception: IOException) {
|
||||||
Toast.makeText(getApplication(), getApplication<PixelDroidApplication>().getString(R.string.upload_post_error),
|
Toast.makeText(applicationContext, applicationContext.getString(R.string.upload_post_error),
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
Log.e(TAG, exception.toString())
|
Log.e(TAG, exception.toString())
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
|
@ -564,7 +584,7 @@ class PostCreationViewModel(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (exception: HttpException) {
|
} catch (exception: HttpException) {
|
||||||
Toast.makeText(getApplication(), getApplication<PixelDroidApplication>().getString(R.string.upload_post_failed),
|
Toast.makeText(applicationContext, applicationContext.getString(R.string.upload_post_failed),
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
Log.e(TAG, exception.response().toString() + exception.message().toString())
|
Log.e(TAG, exception.response().toString() + exception.message().toString())
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
|
@ -609,7 +629,7 @@ class PostCreationViewModel(
|
||||||
|
|
||||||
//Show message saying extraneous pictures were removed but can be restored
|
//Show message saying extraneous pictures were removed but can be restored
|
||||||
newUiState = newUiState.copy(
|
newUiState = newUiState.copy(
|
||||||
userMessage = getApplication<PixelDroidApplication>().getString(R.string.extraneous_pictures_stories)
|
userMessage = applicationContext.getString(R.string.extraneous_pictures_stories)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Restore if backup not null and first value is unchanged
|
// Restore if backup not null and first value is unchanged
|
||||||
|
@ -629,10 +649,4 @@ class PostCreationViewModel(
|
||||||
fun updateStoryReactions(checked: Boolean) { _uiState.update { it.copy(storyReactions = checked) } }
|
fun updateStoryReactions(checked: Boolean) { _uiState.update { it.copy(storyReactions = checked) } }
|
||||||
|
|
||||||
fun updateStoryReplies(checked: Boolean) { _uiState.update { it.copy(storyReplies = checked) } }
|
fun updateStoryReplies(checked: Boolean) { _uiState.update { it.copy(storyReplies = checked) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostCreationViewModelFactory(val application: Application, val clipdata: ClipData, val instance: InstanceDatabaseEntity, val existingDescription: String?, val existingNSFW: Boolean, val storyCreation: Boolean) : ViewModelProvider.Factory {
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return modelClass.getConstructor(Application::class.java, ClipData::class.java, InstanceDatabaseEntity::class.java, String::class.java, Boolean::class.java, Boolean::class.java).newInstance(application, clipdata, instance, existingDescription, existingNSFW, storyCreation)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -38,7 +38,7 @@ class PostSubmissionFragment : BaseFragment() {
|
||||||
private lateinit var instance: InstanceDatabaseEntity
|
private lateinit var instance: InstanceDatabaseEntity
|
||||||
|
|
||||||
private var binding: FragmentPostSubmissionBinding by bindingLifecycleAware()
|
private var binding: FragmentPostSubmissionBinding by bindingLifecycleAware()
|
||||||
private lateinit var model: PostCreationViewModel
|
private val model: PostCreationViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
@ -60,23 +60,9 @@ class PostSubmissionFragment : BaseFragment() {
|
||||||
accounts = db.userDao().getAll()
|
accounts = db.userDao().getAll()
|
||||||
|
|
||||||
instance = user?.run {
|
instance = user?.run {
|
||||||
db.instanceDao().getAll().first { instanceDatabaseEntity ->
|
db.instanceDao().getInstance(instance_uri)
|
||||||
instanceDatabaseEntity.uri.contains(instance_uri)
|
|
||||||
}
|
|
||||||
} ?: InstanceDatabaseEntity("", "")
|
} ?: InstanceDatabaseEntity("", "")
|
||||||
|
|
||||||
val _model: PostCreationViewModel by activityViewModels {
|
|
||||||
PostCreationViewModelFactory(
|
|
||||||
requireActivity().application,
|
|
||||||
requireActivity().intent.clipData!!,
|
|
||||||
instance,
|
|
||||||
requireActivity().intent.getStringExtra(PostCreationActivity.PICTURE_DESCRIPTION),
|
|
||||||
requireActivity().intent.getBooleanExtra(PostCreationActivity.POST_NSFW, false),
|
|
||||||
requireActivity().intent.getBooleanExtra(CameraFragment.CAMERA_ACTIVITY_STORY, false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
model = _model
|
|
||||||
|
|
||||||
// Display the values from the view model
|
// Display the values from the view model
|
||||||
binding.nsfwSwitch.isChecked = model.uiState.value.nsfw
|
binding.nsfwSwitch.isChecked = model.uiState.value.nsfw
|
||||||
binding.newPostDescriptionInputField.setText(model.uiState.value.newPostDescriptionText)
|
binding.newPostDescriptionInputField.setText(model.uiState.value.newPostDescriptionText)
|
||||||
|
|
|
@ -32,7 +32,7 @@ class CameraActivity : BaseActivity() {
|
||||||
// If this CameraActivity wasn't started from the shortcut,
|
// If this CameraActivity wasn't started from the shortcut,
|
||||||
// tell the fragment it's in an activity (so that it sends back the result instead of
|
// tell the fragment it's in an activity (so that it sends back the result instead of
|
||||||
// starting a new post creation process)
|
// starting a new post creation process)
|
||||||
if (intent.action != "android.intent.action.VIEW") {
|
if (intent.action != Intent.ACTION_VIEW) {
|
||||||
val arguments = Bundle()
|
val arguments = Bundle()
|
||||||
arguments.putBoolean(CAMERA_ACTIVITY, true)
|
arguments.putBoolean(CAMERA_ACTIVITY, true)
|
||||||
arguments.putBoolean(CAMERA_ACTIVITY_STORY, story)
|
arguments.putBoolean(CAMERA_ACTIVITY_STORY, story)
|
||||||
|
@ -47,7 +47,7 @@ class CameraActivity : BaseActivity() {
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
// If this CameraActivity wasn't started from the shortcut, behave as usual
|
// If this CameraActivity wasn't started from the shortcut, behave as usual
|
||||||
if (intent.action != "android.intent.action.VIEW") return super.onOptionsItemSelected(item)
|
if (intent.action != Intent.ACTION_VIEW) return super.onOptionsItemSelected(item)
|
||||||
|
|
||||||
// Else, start a new MainActivity when "going back" on this activity
|
// Else, start a new MainActivity when "going back" on this activity
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.pixeldroid.app.postCreation.camera
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
@ -39,7 +38,6 @@ import kotlinx.coroutines.launch
|
||||||
import org.pixeldroid.app.databinding.FragmentCameraBinding
|
import org.pixeldroid.app.databinding.FragmentCameraBinding
|
||||||
import org.pixeldroid.app.postCreation.PostCreationActivity
|
import org.pixeldroid.app.postCreation.PostCreationActivity
|
||||||
import org.pixeldroid.app.utils.BaseFragment
|
import org.pixeldroid.app.utils.BaseFragment
|
||||||
import org.pixeldroid.app.utils.bindingLifecycleAware
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
@ -60,7 +58,7 @@ class CameraFragment : BaseFragment() {
|
||||||
|
|
||||||
private val cameraLifecycleOwner = CameraLifecycleOwner()
|
private val cameraLifecycleOwner = CameraLifecycleOwner()
|
||||||
|
|
||||||
private var binding: FragmentCameraBinding by bindingLifecycleAware()
|
private lateinit var binding: FragmentCameraBinding
|
||||||
|
|
||||||
private var displayId: Int = -1
|
private var displayId: Int = -1
|
||||||
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
|
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
|
||||||
|
@ -327,7 +325,7 @@ class CameraFragment : BaseFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUploadImage() {
|
private fun setupUploadImage() {
|
||||||
val videoEnabled: Boolean = db.instanceDao().getInstance(db.userDao().getActiveUser()!!.instance_uri).videoEnabled
|
val videoEnabled: Boolean = db.instanceDao().getActiveInstance().videoEnabled
|
||||||
var mimeTypes: Array<String> = arrayOf("image/*")
|
var mimeTypes: Array<String> = arrayOf("image/*")
|
||||||
if(videoEnabled) mimeTypes += "video/*"
|
if(videoEnabled) mimeTypes += "video/*"
|
||||||
|
|
||||||
|
@ -450,21 +448,7 @@ class CameraFragment : BaseFragment() {
|
||||||
|
|
||||||
private fun startAlbumCreation(uris: ArrayList<String>) {
|
private fun startAlbumCreation(uris: ArrayList<String>) {
|
||||||
|
|
||||||
val intent = Intent(requireActivity(), PostCreationActivity::class.java)
|
val intent = PostCreationActivity.intentForUris(requireContext(), uris.map { it.toUri() })
|
||||||
.apply {
|
|
||||||
uris.forEach{
|
|
||||||
//Why are we using ClipData here? Because the FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
//needs to be applied to the URIs, and this flag only applies to the
|
|
||||||
//Intent's data and any URIs specified in its ClipData.
|
|
||||||
if(clipData == null){
|
|
||||||
clipData = ClipData("", emptyArray(), ClipData.Item(it.toUri()))
|
|
||||||
} else {
|
|
||||||
clipData!!.addItem(ClipData.Item(it.toUri()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(inActivity && !addToStory){
|
if(inActivity && !addToStory){
|
||||||
requireActivity().setResult(Activity.RESULT_OK, intent)
|
requireActivity().setResult(Activity.RESULT_OK, intent)
|
||||||
|
|
|
@ -3,40 +3,99 @@ package org.pixeldroid.app.posts
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.pixeldroid.app.databinding.ActivityAlbumBinding
|
import org.pixeldroid.app.databinding.ActivityAlbumBinding
|
||||||
import org.pixeldroid.app.utils.api.objects.Attachment
|
|
||||||
|
|
||||||
|
|
||||||
class AlbumActivity : AppCompatActivity() {
|
class AlbumActivity : AppCompatActivity() {
|
||||||
|
private val model: AlbumViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val binding = ActivityAlbumBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
|
val binding = ActivityAlbumBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
val mediaAttachments = intent.getSerializableExtra("images") as ArrayList<Attachment>
|
|
||||||
val index = intent.getIntExtra("index", 0)
|
binding.albumPager.adapter = AlbumViewPagerAdapter(
|
||||||
binding.albumPager.adapter = AlbumViewPagerAdapter(mediaAttachments,
|
model.uiState.value.mediaAttachments,
|
||||||
sensitive = false,
|
sensitive = false,
|
||||||
opened = true,
|
opened = true,
|
||||||
//In the activity, we assume we want to show everything
|
//In the activity, we assume we want to show everything
|
||||||
alwaysShowNsfw = true
|
alwaysShowNsfw = true,
|
||||||
|
clickCallback = ::clickCallback
|
||||||
)
|
)
|
||||||
binding.albumPager.currentItem = index
|
|
||||||
|
|
||||||
if(mediaAttachments.size == 1){
|
binding.albumPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
|
override fun onPageSelected(position: Int) { model.positionSelected(position) }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (model.uiState.value.mediaAttachments.size == 1) {
|
||||||
binding.albumPager.isUserInputEnabled = false
|
binding.albumPager.isUserInputEnabled = false
|
||||||
}
|
} else if ((model.uiState.value.mediaAttachments.size) > 1) {
|
||||||
else if((mediaAttachments.size) > 1) {
|
|
||||||
binding.postIndicator.setViewPager(binding.albumPager)
|
binding.postIndicator.setViewPager(binding.albumPager)
|
||||||
binding.postIndicator.visibility = View.VISIBLE
|
binding.postIndicator.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
binding.postIndicator.visibility = View.GONE
|
binding.postIndicator.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not really necessary because the ViewPager saves its state in onSaveInstanceState, but
|
||||||
|
// it's good to stay consistent in case something gets out of sync
|
||||||
|
binding.albumPager.setCurrentItem(model.uiState.value.index, false)
|
||||||
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||||
supportActionBar?.setBackgroundDrawable(null)
|
supportActionBar?.setBackgroundDrawable(null)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
model.uiState.collect { uiState ->
|
||||||
|
binding.albumPager.currentItem = uiState.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
model.isActionBarHidden.collect { isActionBarHidden ->
|
||||||
|
val windowInsetsController =
|
||||||
|
WindowCompat.getInsetsController(this@AlbumActivity.window, binding.albumPager)
|
||||||
|
if (isActionBarHidden) {
|
||||||
|
// Configure the behavior of the hidden system bars
|
||||||
|
windowInsetsController.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
// Hide both the status bar and the navigation bar
|
||||||
|
supportActionBar?.hide()
|
||||||
|
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||||
|
binding.postIndicator.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
// Configure the behavior of the hidden system bars
|
||||||
|
windowInsetsController.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
// Show both the status bar and the navigation bar
|
||||||
|
supportActionBar?.show()
|
||||||
|
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
if ((model.uiState.value.mediaAttachments.size) > 1) {
|
||||||
|
binding.postIndicator.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback passed to the AlbumViewPagerAdapter to signal a single click on the image
|
||||||
|
*/
|
||||||
|
private fun clickCallback(){
|
||||||
|
model.barHide()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.pixeldroid.app.posts
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import org.pixeldroid.app.utils.api.objects.Attachment
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
data class AlbumUiState(
|
||||||
|
val mediaAttachments: ArrayList<Attachment> = arrayListOf(),
|
||||||
|
val index: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AlbumViewModel @Inject constructor(state: SavedStateHandle) : ViewModel() {
|
||||||
|
companion object {
|
||||||
|
const val ALBUM_IMAGES = "AlbumViewImages"
|
||||||
|
const val ALBUM_INDEX = "AlbumViewIndex"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _uiState: MutableStateFlow<AlbumUiState>
|
||||||
|
private val _isActionBarHidden: MutableStateFlow<Boolean>
|
||||||
|
|
||||||
|
init {
|
||||||
|
_uiState = MutableStateFlow(AlbumUiState(
|
||||||
|
mediaAttachments = state[ALBUM_IMAGES] ?: ArrayList(),
|
||||||
|
index = state[ALBUM_INDEX] ?: 0
|
||||||
|
))
|
||||||
|
_isActionBarHidden = MutableStateFlow(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val uiState: StateFlow<AlbumUiState> = _uiState.asStateFlow()
|
||||||
|
val isActionBarHidden: StateFlow<Boolean> = _isActionBarHidden
|
||||||
|
|
||||||
|
fun barHide() {
|
||||||
|
_isActionBarHidden.update { !it }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun positionSelected(position: Int) {
|
||||||
|
_uiState.update { it.copy(index = position) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,8 +88,8 @@ class NestedScrollableHost(context: Context, attrs: AttributeSet? = null) :
|
||||||
}
|
}
|
||||||
val intent = Intent(context, AlbumActivity::class.java)
|
val intent = Intent(context, AlbumActivity::class.java)
|
||||||
|
|
||||||
intent.putExtra("images", images)
|
intent.putExtra(AlbumViewModel.ALBUM_IMAGES, images)
|
||||||
intent.putExtra("index", (child as ViewPager2).currentItem)
|
intent.putExtra(AlbumViewModel.ALBUM_INDEX, (child as ViewPager2).currentItem)
|
||||||
|
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.view.View
|
||||||
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
|
@ -24,13 +25,16 @@ import org.pixeldroid.app.utils.displayDimensionsInPx
|
||||||
class PostActivity : BaseActivity() {
|
class PostActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityPostBinding
|
private lateinit var binding: ActivityPostBinding
|
||||||
|
|
||||||
private var commentFragment = CommentFragment()
|
private lateinit var commentFragment: CommentFragment
|
||||||
|
|
||||||
private lateinit var status: Status
|
private lateinit var status: Status
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityPostBinding.inflate(layoutInflater)
|
binding = ActivityPostBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
commentFragment = CommentFragment(binding.swipeRefreshLayout)
|
||||||
|
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
setSupportActionBar(binding.topBar)
|
setSupportActionBar(binding.topBar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
@ -105,6 +109,11 @@ class PostActivity : BaseActivity() {
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.add(R.id.commentFragment, commentFragment).commit()
|
.add(R.id.commentFragment, commentFragment).commit()
|
||||||
|
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
|
commentFragment.adapter.refresh()
|
||||||
|
commentFragment.adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun postComment(
|
private suspend fun postComment(
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
package org.pixeldroid.app.posts
|
package org.pixeldroid.app.posts
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager.PERMISSION_DENIED
|
import android.content.pm.PackageManager.PERMISSION_DENIED
|
||||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
import android.graphics.drawable.AnimatedVectorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
@ -20,11 +17,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -77,7 +70,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
fun bind(
|
fun bind(
|
||||||
status: Status?, pixelfedAPI: PixelfedAPIHolder, db: AppDatabase,
|
status: Status?, pixelfedAPI: PixelfedAPIHolder, db: AppDatabase,
|
||||||
lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>,
|
lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>,
|
||||||
requestPermissionDownloadPic: ActivityResultLauncher<String>, isActivity: Boolean = false
|
requestPermissionDownloadPic: ActivityResultLauncher<String>, isActivity: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.itemView.visibility = View.VISIBLE
|
this.itemView.visibility = View.VISIBLE
|
||||||
|
@ -371,7 +364,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
apiHolder: PixelfedAPIHolder,
|
apiHolder: PixelfedAPIHolder,
|
||||||
db: AppDatabase,
|
db: AppDatabase,
|
||||||
lifecycleScope: LifecycleCoroutineScope,
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
requestPermissionDownloadPic: ActivityResultLauncher<String>
|
requestPermissionDownloadPic: ActivityResultLauncher<String>,
|
||||||
){
|
){
|
||||||
var bookmarked: Boolean? = null
|
var bookmarked: Boolean? = null
|
||||||
binding.statusMore.setOnClickListener {
|
binding.statusMore.setOnClickListener {
|
||||||
|
@ -449,178 +442,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.post_more_menu_redraft -> {
|
R.id.post_more_menu_redraft -> launchRedraftDialog(lifecycleScope, apiHolder, db)
|
||||||
MaterialAlertDialogBuilder(binding.root.context).apply {
|
|
||||||
setMessage(R.string.redraft_dialog_launch)
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
try {
|
|
||||||
// Create new post creation activity
|
|
||||||
val intent =
|
|
||||||
Intent(context, PostCreationActivity::class.java)
|
|
||||||
|
|
||||||
// Get descriptions and images from original post
|
|
||||||
val postDescription = status?.content ?: ""
|
|
||||||
val postAttachments =
|
|
||||||
status?.media_attachments!! // Catch possible exception from !! (?)
|
|
||||||
val postNSFW = status?.sensitive
|
|
||||||
|
|
||||||
val imageUriStrings = postAttachments.map { postAttachment ->
|
|
||||||
postAttachment.url ?: ""
|
|
||||||
}
|
|
||||||
val imageNames = imageUriStrings.map { imageUriString ->
|
|
||||||
Uri.parse(imageUriString).lastPathSegment.toString()
|
|
||||||
}
|
|
||||||
val downloadedFiles = imageNames.map { imageName ->
|
|
||||||
File(context.cacheDir, imageName)
|
|
||||||
}
|
|
||||||
val imageUris = downloadedFiles.map { downloadedFile ->
|
|
||||||
Uri.fromFile(downloadedFile)
|
|
||||||
}
|
|
||||||
val imageDescriptions = postAttachments.map { postAttachment ->
|
|
||||||
fromHtml(postAttachment.description ?: "").toString()
|
|
||||||
}
|
|
||||||
val downloadRequests: List<Request> = imageUriStrings.map { imageUriString ->
|
|
||||||
Request.Builder().url(imageUriString).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
val counter = AtomicInteger(0)
|
|
||||||
|
|
||||||
// Define callback function for after downloading the images
|
|
||||||
fun continuation() {
|
|
||||||
// Wait for all outstanding downloads to finish
|
|
||||||
if (counter.incrementAndGet() == imageUris.size) {
|
|
||||||
if (allFilesExist(imageNames)) {
|
|
||||||
// Delete original post
|
|
||||||
lifecycleScope.launch {
|
|
||||||
deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db)
|
|
||||||
}
|
|
||||||
|
|
||||||
val counterInt = counter.get()
|
|
||||||
Toast.makeText(
|
|
||||||
binding.root.context,
|
|
||||||
binding.root.context.resources.getQuantityString(
|
|
||||||
R.plurals.items_load_success,
|
|
||||||
counterInt,
|
|
||||||
counterInt
|
|
||||||
),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
// Pass downloaded images to new post creation activity
|
|
||||||
intent.apply {
|
|
||||||
imageUris.zip(imageDescriptions).map { (imageUri, imageDescription) ->
|
|
||||||
ClipData.Item(imageDescription, null, imageUri)
|
|
||||||
}.forEach { imageItem ->
|
|
||||||
if (clipData == null) {
|
|
||||||
clipData = ClipData(
|
|
||||||
"",
|
|
||||||
emptyArray(),
|
|
||||||
imageItem
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
clipData!!.addItem(imageItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass post description of existing post to new post creation activity
|
|
||||||
intent.putExtra(
|
|
||||||
PostCreationActivity.PICTURE_DESCRIPTION,
|
|
||||||
fromHtml(postDescription).toString()
|
|
||||||
)
|
|
||||||
if (imageNames.isNotEmpty()) {
|
|
||||||
intent.putExtra(
|
|
||||||
PostCreationActivity.TEMP_FILES,
|
|
||||||
imageNames.toTypedArray()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
intent.putExtra(
|
|
||||||
PostCreationActivity.POST_REDRAFT,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
intent.putExtra(
|
|
||||||
PostCreationActivity.POST_NSFW,
|
|
||||||
postNSFW
|
|
||||||
)
|
|
||||||
|
|
||||||
// Launch post creation activity
|
|
||||||
binding.root.context.startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allFilesExist(imageNames)) {
|
|
||||||
// Track download progress
|
|
||||||
Toast.makeText(
|
|
||||||
binding.root.context,
|
|
||||||
binding.root.context.getString(R.string.image_download_downloading),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through all pictures of the original post
|
|
||||||
downloadRequests.zip(downloadedFiles).forEach { (downloadRequest, downloadedFile) ->
|
|
||||||
// Check whether image is in cache directory already (maybe rather do so using Glide in the future?)
|
|
||||||
if (!downloadedFile.exists()) {
|
|
||||||
OkHttpClient().newCall(downloadRequest)
|
|
||||||
.enqueue(object : Callback {
|
|
||||||
override fun onFailure(
|
|
||||||
call: Call,
|
|
||||||
e: IOException
|
|
||||||
) {
|
|
||||||
Looper.prepare()
|
|
||||||
downloadedFile.delete()
|
|
||||||
Toast.makeText(
|
|
||||||
binding.root.context,
|
|
||||||
binding.root.context.getString(R.string.redraft_post_failed_io_except),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun onResponse(
|
|
||||||
call: Call,
|
|
||||||
response: Response
|
|
||||||
) {
|
|
||||||
val sink: BufferedSink =
|
|
||||||
downloadedFile.sink().buffer()
|
|
||||||
sink.writeAll(response.body!!.source())
|
|
||||||
sink.close()
|
|
||||||
Looper.prepare()
|
|
||||||
continuation()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
continuation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (exception: HttpException) {
|
|
||||||
Toast.makeText(
|
|
||||||
binding.root.context,
|
|
||||||
binding.root.context.getString(
|
|
||||||
R.string.redraft_post_failed_error,
|
|
||||||
exception.code()
|
|
||||||
),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
} catch (exception: IOException) {
|
|
||||||
Toast.makeText(
|
|
||||||
binding.root.context,
|
|
||||||
binding.root.context.getString(R.string.redraft_post_failed_io_except),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -645,6 +467,165 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchRedraftDialog(
|
||||||
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
|
apiHolder: PixelfedAPIHolder,
|
||||||
|
db: AppDatabase
|
||||||
|
): Boolean {
|
||||||
|
MaterialAlertDialogBuilder(binding.root.context).apply {
|
||||||
|
setMessage(R.string.redraft_dialog_launch)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
// Get descriptions and images from original post
|
||||||
|
val postDescription = status?.content ?: ""
|
||||||
|
val postAttachments =
|
||||||
|
status?.media_attachments!! // TODO Catch possible exception from !! (?)
|
||||||
|
val postNSFW = status?.sensitive
|
||||||
|
|
||||||
|
val imageUriStrings = postAttachments.map { postAttachment ->
|
||||||
|
postAttachment.url ?: ""
|
||||||
|
}
|
||||||
|
val imageNames = imageUriStrings.map { imageUriString ->
|
||||||
|
Uri.parse(imageUriString).lastPathSegment.toString()
|
||||||
|
}
|
||||||
|
val downloadedFiles = imageNames.map { imageName ->
|
||||||
|
File(context.cacheDir, imageName)
|
||||||
|
}
|
||||||
|
val imageDescriptions = postAttachments.map { postAttachment ->
|
||||||
|
fromHtml(
|
||||||
|
postAttachment.description ?: ""
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
val downloadRequests: List<Request> =
|
||||||
|
imageUriStrings.map { imageUriString ->
|
||||||
|
Request.Builder().url(imageUriString).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val imageUris = downloadedFiles.map { downloadedFile ->
|
||||||
|
Uri.fromFile(downloadedFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
|
||||||
|
// Define callback function for after downloading the images
|
||||||
|
fun continuation() {
|
||||||
|
// Wait for all outstanding downloads to finish
|
||||||
|
if (counter.incrementAndGet() == imageUris.size) {
|
||||||
|
if (allFilesExist(imageNames)) {
|
||||||
|
// Delete original post
|
||||||
|
lifecycleScope.launch {
|
||||||
|
deletePost(
|
||||||
|
apiHolder.api ?: apiHolder.setToCurrentUser(), db
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val counterInt = counter.get()
|
||||||
|
Toast.makeText(
|
||||||
|
binding.root.context,
|
||||||
|
binding.root.context.resources.getQuantityString(
|
||||||
|
R.plurals.items_load_success, counterInt, counterInt
|
||||||
|
),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
|
||||||
|
// Create new post creation activity
|
||||||
|
val intent = PostCreationActivity.intentForUris(context, imageUris).apply {
|
||||||
|
putExtra(
|
||||||
|
PostCreationActivity.PICTURE_DESCRIPTIONS,
|
||||||
|
ArrayList(imageDescriptions)
|
||||||
|
)
|
||||||
|
// Pass post description of existing post to new post creation activity
|
||||||
|
putExtra(
|
||||||
|
PostCreationActivity.POST_DESCRIPTION,
|
||||||
|
fromHtml(postDescription).toString()
|
||||||
|
)
|
||||||
|
if (imageNames.isNotEmpty()) {
|
||||||
|
putExtra(
|
||||||
|
PostCreationActivity.TEMP_FILES,
|
||||||
|
imageNames.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
putExtra(PostCreationActivity.POST_REDRAFT, true)
|
||||||
|
putExtra(PostCreationActivity.POST_NSFW, postNSFW)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch post creation activity
|
||||||
|
binding.root.context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allFilesExist(imageNames)) {
|
||||||
|
// Track download progress
|
||||||
|
Toast.makeText(
|
||||||
|
binding.root.context,
|
||||||
|
binding.root.context.getString(R.string.image_download_downloading),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through all pictures of the original post
|
||||||
|
downloadRequests.zip(downloadedFiles)
|
||||||
|
.forEach { (downloadRequest, downloadedFile) ->
|
||||||
|
// Check whether image is in cache directory already (maybe rather do so using Glide in the future?)
|
||||||
|
if (!downloadedFile.exists()) {
|
||||||
|
OkHttpClient().newCall(downloadRequest)
|
||||||
|
.enqueue(object : Callback {
|
||||||
|
override fun onFailure(
|
||||||
|
call: Call,
|
||||||
|
e: IOException,
|
||||||
|
) {
|
||||||
|
Looper.prepare()
|
||||||
|
downloadedFile.delete()
|
||||||
|
Toast.makeText(
|
||||||
|
binding.root.context,
|
||||||
|
binding.root.context.getString(
|
||||||
|
R.string.redraft_post_failed_io_except
|
||||||
|
),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call,
|
||||||
|
response: Response,
|
||||||
|
) {
|
||||||
|
val sink: BufferedSink =
|
||||||
|
downloadedFile.sink().buffer()
|
||||||
|
sink.writeAll(response.body!!.source())
|
||||||
|
sink.close()
|
||||||
|
Looper.prepare()
|
||||||
|
continuation()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
continuation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: HttpException) {
|
||||||
|
Toast.makeText(
|
||||||
|
binding.root.context, binding.root.context.getString(
|
||||||
|
R.string.redraft_post_failed_error, exception.code()
|
||||||
|
), Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
Toast.makeText(
|
||||||
|
binding.root.context,
|
||||||
|
binding.root.context.getString(R.string.redraft_post_failed_io_except),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun activateLiker(
|
private fun activateLiker(
|
||||||
apiHolder: PixelfedAPIHolder,
|
apiHolder: PixelfedAPIHolder,
|
||||||
isLiked: Boolean,
|
isLiked: Boolean,
|
||||||
|
@ -820,17 +801,15 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
class AlbumViewPagerAdapter(
|
class AlbumViewPagerAdapter(
|
||||||
private val media_attachments: List<Attachment>, private var sensitive: Boolean?,
|
private val media_attachments: List<Attachment>, private var sensitive: Boolean?,
|
||||||
private val opened: Boolean, private val alwaysShowNsfw: Boolean,
|
private val opened: Boolean, private val alwaysShowNsfw: Boolean,
|
||||||
) :
|
private val clickCallback: (() -> Unit)? = null
|
||||||
RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var isActionBarHidden: Boolean = false
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return if(!opened) ViewHolderClosed(AlbumImageViewBinding.inflate(
|
return if(!opened) ViewHolderClosed(AlbumImageViewBinding.inflate(
|
||||||
LayoutInflater.from(parent.context), parent, false
|
LayoutInflater.from(parent.context), parent, false
|
||||||
)) else ViewHolderOpen(OpenedAlbumBinding.inflate(
|
)) else ViewHolderOpen(OpenedAlbumBinding.inflate(
|
||||||
LayoutInflater.from(parent.context), parent, false
|
LayoutInflater.from(parent.context), parent, false
|
||||||
))
|
), clickCallback!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = media_attachments.size
|
override fun getItemCount() = media_attachments.size
|
||||||
|
@ -861,24 +840,6 @@ class AlbumViewPagerAdapter(
|
||||||
setDoubleTapZoomDpi(240)
|
setDoubleTapZoomDpi(240)
|
||||||
resetScaleAndCenter()
|
resetScaleAndCenter()
|
||||||
}
|
}
|
||||||
holder.image.setOnClickListener {
|
|
||||||
val windowInsetsController = WindowCompat.getInsetsController((it.context as Activity).window, it)
|
|
||||||
// Configure the behavior of the hidden system bars
|
|
||||||
if (isActionBarHidden) {
|
|
||||||
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
// Hide both the status bar and the navigation bar
|
|
||||||
(it.context as AppCompatActivity).supportActionBar?.show()
|
|
||||||
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
|
||||||
isActionBarHidden = false
|
|
||||||
} else {
|
|
||||||
// Configure the behavior of the hidden system bars
|
|
||||||
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
// Hide both the status bar and the navigation bar
|
|
||||||
(it.context as AppCompatActivity).supportActionBar?.hide()
|
|
||||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
|
||||||
isActionBarHidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else Glide.with(holder.binding.root)
|
else Glide.with(holder.binding.root)
|
||||||
.asDrawable().fitCenter()
|
.asDrawable().fitCenter()
|
||||||
|
@ -924,9 +885,13 @@ class AlbumViewPagerAdapter(
|
||||||
abstract val videoPlayButton: ImageView
|
abstract val videoPlayButton: ImageView
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolderOpen(override val binding: OpenedAlbumBinding) : ViewHolder(binding) {
|
class ViewHolderOpen(override val binding: OpenedAlbumBinding, clickCallback: () -> Unit) : ViewHolder(binding) {
|
||||||
override val image: SubsamplingScaleImageView = binding.imageImageView
|
override val image: SubsamplingScaleImageView = binding.imageImageView
|
||||||
override val videoPlayButton: ImageView = binding.videoPlayButton
|
override val videoPlayButton: ImageView = binding.videoPlayButton
|
||||||
|
|
||||||
|
init {
|
||||||
|
image.setOnClickListener { clickCallback() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
class ViewHolderClosed(override val binding: AlbumImageViewBinding) : ViewHolder(binding) {
|
class ViewHolderClosed(override val binding: AlbumImageViewBinding) : ViewHolder(binding) {
|
||||||
override val image: ImageView = binding.imageImageView
|
override val image: ImageView = binding.imageImageView
|
||||||
|
|
|
@ -73,6 +73,7 @@ internal fun <T: Any> initAdapter(
|
||||||
|
|
||||||
swipeRefreshLayout.setOnRefreshListener {
|
swipeRefreshLayout.setOnRefreshListener {
|
||||||
adapter.refresh()
|
adapter.refresh()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
header?.refreshStories()
|
header?.refreshStories()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,18 +16,20 @@
|
||||||
|
|
||||||
package org.pixeldroid.app.posts.feeds.cachedFeeds
|
package org.pixeldroid.app.posts.feeds.cachedFeeds
|
||||||
|
|
||||||
import androidx.paging.*
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.RemoteMediator
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
|
||||||
import org.pixeldroid.app.utils.db.AppDatabase
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
|
import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
|
||||||
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository class that works with local and remote data sources.
|
* Repository class that works with local and remote data sources.
|
||||||
*/
|
*/
|
||||||
class FeedContentRepository<T: FeedContentDatabase> @ExperimentalPagingApi
|
class FeedContentRepository<T: FeedContentDatabase> @ExperimentalPagingApi constructor(
|
||||||
@Inject constructor(
|
|
||||||
private val db: AppDatabase,
|
private val db: AppDatabase,
|
||||||
private val dao: FeedContentDao<T>,
|
private val dao: FeedContentDao<T>,
|
||||||
private val mediator: RemoteMediator<Int, T>
|
private val mediator: RemoteMediator<Int, T>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
|
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
|
||||||
|
|
||||||
import androidx.paging.*
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.LoadType
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import androidx.paging.RemoteMediator
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import org.pixeldroid.app.utils.db.AppDatabase
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
|
||||||
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
|
||||||
import java.lang.NullPointerException
|
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +19,7 @@ import javax.inject.Inject
|
||||||
* a local db cache.
|
* a local db cache.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalPagingApi::class)
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
class HomeFeedRemoteMediator @Inject constructor(
|
class HomeFeedRemoteMediator(
|
||||||
private val apiHolder: PixelfedAPIHolder,
|
private val apiHolder: PixelfedAPIHolder,
|
||||||
private val db: AppDatabase,
|
private val db: AppDatabase,
|
||||||
) : RemoteMediator<Int, HomeStatusDatabaseEntity>() {
|
) : RemoteMediator<Int, HomeStatusDatabaseEntity>() {
|
||||||
|
|
|
@ -16,13 +16,15 @@
|
||||||
|
|
||||||
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
|
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
|
||||||
|
|
||||||
import androidx.paging.*
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.LoadType
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import androidx.paging.RemoteMediator
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import org.pixeldroid.app.utils.db.AppDatabase
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
|
||||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||||
import java.lang.NullPointerException
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RemoteMediator for the public feed.
|
* RemoteMediator for the public feed.
|
||||||
|
@ -32,7 +34,7 @@ import javax.inject.Inject
|
||||||
* a local db cache.
|
* a local db cache.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalPagingApi::class)
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
class PublicFeedRemoteMediator @Inject constructor(
|
class PublicFeedRemoteMediator(
|
||||||
private val apiHolder: PixelfedAPIHolder,
|
private val apiHolder: PixelfedAPIHolder,
|
||||||
private val db: AppDatabase
|
private val db: AppDatabase
|
||||||
) : RemoteMediator<Int, PublicFeedStatusDatabaseEntity>() {
|
) : RemoteMediator<Int, PublicFeedStatusDatabaseEntity>() {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.paging.ExperimentalPagingApi
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.PagingDataAdapter
|
import androidx.paging.PagingDataAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
@ -20,6 +21,7 @@ import org.pixeldroid.app.posts.feeds.initAdapter
|
||||||
import org.pixeldroid.app.posts.feeds.launch
|
import org.pixeldroid.app.posts.feeds.launch
|
||||||
import org.pixeldroid.app.utils.BaseFragment
|
import org.pixeldroid.app.utils.BaseFragment
|
||||||
import org.pixeldroid.app.utils.api.objects.FeedContent
|
import org.pixeldroid.app.utils.api.objects.FeedContent
|
||||||
|
import org.pixeldroid.app.utils.limitedLengthSmoothScrollToPosition
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,8 +32,7 @@ open class UncachedFeedFragment<T: FeedContent> : BaseFragment() {
|
||||||
internal lateinit var viewModel: FeedViewModel<T>
|
internal lateinit var viewModel: FeedViewModel<T>
|
||||||
internal lateinit var adapter: PagingDataAdapter<T, RecyclerView.ViewHolder>
|
internal lateinit var adapter: PagingDataAdapter<T, RecyclerView.ViewHolder>
|
||||||
|
|
||||||
lateinit var binding: FragmentFeedBinding
|
var binding: FragmentFeedBinding? = null
|
||||||
|
|
||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
|
@ -48,25 +49,35 @@ open class UncachedFeedFragment<T: FeedContent> : BaseFragment() {
|
||||||
.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 LoadState.NotLoading }
|
||||||
.collect { binding.list.scrollToPosition(0) }
|
.collect { binding?.list?.scrollToPosition(0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?, swipeRefreshLayout: SwipeRefreshLayout?
|
||||||
): View? {
|
): View {
|
||||||
|
|
||||||
super.onCreateView(inflater, container, savedInstanceState)
|
super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
|
||||||
binding = FragmentFeedBinding.inflate(layoutInflater)
|
binding = FragmentFeedBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
initAdapter(
|
binding!!.let {
|
||||||
binding.progressBar, binding.swipeRefreshLayout, binding.list,
|
initAdapter(
|
||||||
binding.motionLayout, binding.errorLayout, adapter
|
it.progressBar, swipeRefreshLayout ?: it.swipeRefreshLayout, it.list,
|
||||||
)
|
it.motionLayout, it.errorLayout, adapter
|
||||||
|
)
|
||||||
|
|
||||||
return binding.root
|
}
|
||||||
|
return binding!!.root
|
||||||
|
}
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return onCreateView(inflater, container, savedInstanceState, null)
|
||||||
|
}
|
||||||
|
fun onTabReClicked() {
|
||||||
|
binding?.list?.limitedLengthSmoothScrollToPosition(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,15 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.NestedScrollingChild
|
||||||
|
import androidx.core.view.NestedScrollingChildHelper
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.paging.ExperimentalPagingApi
|
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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.databinding.CommentBinding
|
import org.pixeldroid.app.databinding.CommentBinding
|
||||||
import org.pixeldroid.app.posts.PostActivity
|
import org.pixeldroid.app.posts.PostActivity
|
||||||
|
@ -25,7 +28,7 @@ import org.pixeldroid.app.utils.setProfileImageFromURL
|
||||||
/**
|
/**
|
||||||
* Fragment to show a list of [Status]s, in form of comments
|
* Fragment to show a list of [Status]s, in form of comments
|
||||||
*/
|
*/
|
||||||
class CommentFragment : UncachedFeedFragment<Status>() {
|
class CommentFragment(val swipeRefreshLayout: SwipeRefreshLayout): UncachedFeedFragment<Status>() {
|
||||||
|
|
||||||
private lateinit var id: String
|
private lateinit var id: String
|
||||||
private lateinit var domain: String
|
private lateinit var domain: String
|
||||||
|
@ -42,11 +45,11 @@ class CommentFragment : UncachedFeedFragment<Status>() {
|
||||||
@OptIn(ExperimentalPagingApi::class)
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View? {
|
||||||
|
|
||||||
|
|
||||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
val view = super.onCreateView(inflater, container, savedInstanceState, swipeRefreshLayout)
|
||||||
|
|
||||||
// Get the view model
|
// Get the view model
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
@ -62,6 +65,7 @@ class CommentFragment : UncachedFeedFragment<Status>() {
|
||||||
launch()
|
launch()
|
||||||
initSearch()
|
initSearch()
|
||||||
|
|
||||||
|
binding?.swipeRefreshLayout?.isEnabled = false
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.pixeldroid.app.utils.openUrl
|
||||||
|
|
||||||
class EditProfileActivity : BaseActivity() {
|
class EditProfileActivity : BaseActivity() {
|
||||||
|
|
||||||
private lateinit var model: EditProfileViewModel
|
private val model: EditProfileViewModel by viewModels()
|
||||||
private lateinit var binding: ActivityEditProfileBinding
|
private lateinit var binding: ActivityEditProfileBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -35,9 +35,6 @@ class EditProfileActivity : BaseActivity() {
|
||||||
setSupportActionBar(binding.topBar)
|
setSupportActionBar(binding.topBar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
val _model: EditProfileViewModel by viewModels { EditProfileViewModelFactory(application) }
|
|
||||||
model = _model
|
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this) {
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
// Handle the back button event
|
// Handle the back button event
|
||||||
if(model.madeChanges()){
|
if(model.madeChanges()){
|
||||||
|
@ -51,6 +48,7 @@ class EditProfileActivity : BaseActivity() {
|
||||||
}.show()
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
this.isEnabled = false
|
this.isEnabled = false
|
||||||
|
if (model.submittedChanges) setResult(RESULT_OK)
|
||||||
super.onBackPressedDispatcher.onBackPressed()
|
super.onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,23 +56,24 @@ class EditProfileActivity : BaseActivity() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
model.uiState.collect { uiState ->
|
model.uiState.collect { uiState ->
|
||||||
if(uiState.profileLoaded){
|
if(binding.bioEditText.text.toString() != uiState.bio) binding.bioEditText.setText(uiState.bio)
|
||||||
binding.bioEditText.setText(uiState.bio)
|
if(binding.nameEditText.text.toString() != uiState.name) binding.nameEditText.setText(uiState.name)
|
||||||
binding.nameEditText.setText(uiState.name)
|
|
||||||
model.changesApplied()
|
binding.progressCard.visibility = if(uiState.loadingProfile || uiState.sendingProfile || uiState.uploadingPicture || uiState.profileSent || uiState.error) View.VISIBLE else View.INVISIBLE
|
||||||
}
|
|
||||||
binding.progressCard.visibility = if(uiState.loadingProfile || uiState.sendingProfile || uiState.profileSent || uiState.error) View.VISIBLE else View.INVISIBLE
|
|
||||||
if(uiState.loadingProfile) binding.progressText.setText(R.string.fetching_profile)
|
if(uiState.loadingProfile) binding.progressText.setText(R.string.fetching_profile)
|
||||||
else if(uiState.sendingProfile) binding.progressText.setText(R.string.saving_profile)
|
else if(uiState.sendingProfile) binding.progressText.setText(R.string.saving_profile)
|
||||||
|
|
||||||
binding.privateSwitch.isChecked = uiState.privateAccount == true
|
binding.privateSwitch.isChecked = uiState.privateAccount == true
|
||||||
Glide.with(binding.profilePic).load(uiState.profilePictureUri)
|
Glide.with(binding.profilePic).load(uiState.profilePictureUri)
|
||||||
.apply(RequestOptions.circleCropTransform())
|
.apply(RequestOptions.circleCropTransform())
|
||||||
.into(binding.profilePic)
|
.into(binding.profilePic)
|
||||||
|
|
||||||
binding.savingProgressBar.visibility = if(uiState.error || uiState.profileSent) View.GONE
|
binding.savingProgressBar.visibility =
|
||||||
else View.VISIBLE
|
if(uiState.error || (uiState.profileSent && !uiState.uploadingPicture)) View.GONE
|
||||||
|
else View.VISIBLE
|
||||||
|
|
||||||
if(uiState.profileSent){
|
if(uiState.profileSent && !uiState.uploadingPicture && !uiState.error){
|
||||||
binding.progressText.setText(R.string.profile_saved)
|
binding.progressText.setText(R.string.profile_saved)
|
||||||
binding.done.visibility = View.VISIBLE
|
binding.done.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,18 +111,18 @@ class EditProfileActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// binding.changeImageButton.setOnClickListener {
|
binding.profilePic.setOnClickListener {
|
||||||
// Intent(Intent.ACTION_GET_CONTENT).apply {
|
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
// type = "*/*"
|
type = "*/*"
|
||||||
// putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*"))
|
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*"))
|
||||||
// action = Intent.ACTION_GET_CONTENT
|
action = Intent.ACTION_GET_CONTENT
|
||||||
// addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
// putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
|
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
|
||||||
// uploadImageResultContract.launch(
|
uploadImageResultContract.launch(
|
||||||
// Intent.createChooser(this, null)
|
Intent.createChooser(this, null)
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val uploadImageResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val uploadImageResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
@ -137,10 +136,10 @@ class EditProfileActivity : BaseActivity() {
|
||||||
val imageUri: String = clipData.getItemAt(i).uri.toString()
|
val imageUri: String = clipData.getItemAt(i).uri.toString()
|
||||||
images.add(imageUri)
|
images.add(imageUri)
|
||||||
}
|
}
|
||||||
model.uploadImage(images.first())
|
model.updateImage(images.first())
|
||||||
} else if (data.data != null) {
|
} else if (data.data != null) {
|
||||||
images.add(data.data!!.toString())
|
images.add(data.data!!.toString())
|
||||||
model.uploadImage(images.first())
|
model.updateImage(images.first())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package org.pixeldroid.app.profile
|
package org.pixeldroid.app.profile
|
||||||
|
|
||||||
import android.app.Application
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
@ -21,23 +21,33 @@ import kotlinx.coroutines.launch
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import org.pixeldroid.app.postCreation.ProgressRequestBody
|
import org.pixeldroid.app.postCreation.ProgressRequestBody
|
||||||
import org.pixeldroid.app.posts.fromHtml
|
import org.pixeldroid.app.posts.fromHtml
|
||||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
|
||||||
import org.pixeldroid.app.utils.api.objects.Account
|
import org.pixeldroid.app.utils.api.objects.Account
|
||||||
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
|
import org.pixeldroid.app.utils.db.updateUserInfoDb
|
||||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||||
|
import retrofit2.HttpException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class EditProfileViewModel(application: Application) : AndroidViewModel(application) {
|
@HiltViewModel
|
||||||
|
class EditProfileViewModel @Inject constructor(
|
||||||
|
@ApplicationContext private val applicationContext: Context
|
||||||
|
): ViewModel() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var apiHolder: PixelfedAPIHolder
|
lateinit var apiHolder: PixelfedAPIHolder
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var db: AppDatabase
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(EditProfileActivityUiState())
|
private val _uiState = MutableStateFlow(EditProfileActivityUiState())
|
||||||
val uiState: StateFlow<EditProfileActivityUiState> = _uiState
|
val uiState: StateFlow<EditProfileActivityUiState> = _uiState
|
||||||
|
|
||||||
var oldProfile: Account? = null
|
private var oldProfile: Account? = null
|
||||||
|
|
||||||
|
var submittedChanges = false
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
(application as PixelDroidApplication).getAppComponent().inject(this)
|
|
||||||
loadProfile()
|
loadProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +56,7 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||||
try {
|
try {
|
||||||
val profile = api.verifyCredentials()
|
val profile = api.verifyCredentials()
|
||||||
|
updateUserInfoDb(db, profile)
|
||||||
if (oldProfile == null) oldProfile = profile
|
if (oldProfile == null) oldProfile = profile
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
|
@ -76,15 +87,10 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
fun sendProfile() {
|
fun sendProfile() {
|
||||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||||
|
|
||||||
val requestBody =
|
|
||||||
null //MultipartBody.Part.createFormData("avatar", System.currentTimeMillis().toString(), avatarBody)
|
|
||||||
|
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
sendingProfile = true,
|
sendingProfile = true,
|
||||||
profileSent = false,
|
profileSent = false,
|
||||||
loadingProfile = false,
|
|
||||||
profileLoaded = false,
|
|
||||||
error = false
|
error = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -97,12 +103,17 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
note = bio,
|
note = bio,
|
||||||
locked = privateAccount,
|
locked = privateAccount,
|
||||||
)
|
)
|
||||||
|
if (madeChanges()) submittedChanges = true
|
||||||
oldProfile = account
|
oldProfile = account
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
bio = account.source?.note ?: account.note?.let {fromHtml(it).toString()},
|
bio = account.source?.note
|
||||||
|
?: account.note?.let { fromHtml(it).toString() },
|
||||||
name = account.display_name,
|
name = account.display_name,
|
||||||
profilePictureUri = account.anyAvatar()?.toUri(),
|
profilePictureUri = if (profilePictureChanged) profilePictureUri
|
||||||
|
else account.anyAvatar()?.toUri(),
|
||||||
|
uploadProgress = 0,
|
||||||
|
uploadingPicture = profilePictureChanged,
|
||||||
privateAccount = account.locked,
|
privateAccount = account.locked,
|
||||||
sendingProfile = false,
|
sendingProfile = false,
|
||||||
profileSent = true,
|
profileSent = true,
|
||||||
|
@ -111,14 +122,13 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
error = false
|
error = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if(profilePictureChanged) uploadImage()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.e("TAG", exception.toString())
|
Log.e("TAG", exception.toString())
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
sendingProfile = false,
|
sendingProfile = false,
|
||||||
profileSent = false,
|
profileSent = false,
|
||||||
loadingProfile = false,
|
|
||||||
profileLoaded = false,
|
|
||||||
error = true
|
error = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -145,20 +155,16 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changesApplied() {
|
|
||||||
_uiState.update { currentUiState ->
|
|
||||||
currentUiState.copy(profileLoaded = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun madeChanges(): Boolean =
|
fun madeChanges(): Boolean =
|
||||||
with(uiState.value) {
|
with(uiState.value) {
|
||||||
val bioUnchanged: Boolean = oldProfile?.source?.note?.let { it != bio }
|
val privateChanged = oldProfile?.locked != privateAccount
|
||||||
// If source note is null, check note
|
val displayNameChanged = oldProfile?.display_name != name
|
||||||
|
val bioChanged: Boolean = oldProfile?.source?.note?.let { it != bio }
|
||||||
|
// If source note is null, check note
|
||||||
?: oldProfile?.note?.let { fromHtml(it).toString() != bio }
|
?: oldProfile?.note?.let { fromHtml(it).toString() != bio }
|
||||||
?: true
|
?: true
|
||||||
oldProfile?.locked != privateAccount || oldProfile?.display_name != name
|
|
||||||
|| bioUnchanged
|
profilePictureChanged || privateChanged || displayNameChanged || bioChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickedCard() {
|
fun clickedCard() {
|
||||||
|
@ -178,16 +184,27 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uploadImage(image: String) {
|
fun updateImage(image: String) {
|
||||||
//TODO fix
|
_uiState.update { currentUiState ->
|
||||||
|
currentUiState.copy(
|
||||||
|
profilePictureUri = image.toUri(),
|
||||||
|
profilePictureChanged = true,
|
||||||
|
profileSent = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uploadImage() {
|
||||||
|
val image = uiState.value.profilePictureUri!!
|
||||||
|
|
||||||
val inputStream =
|
val inputStream =
|
||||||
getApplication<PixelDroidApplication>().contentResolver.openInputStream(image.toUri())
|
applicationContext.contentResolver.openInputStream(image)
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
val size: Long =
|
val size: Long =
|
||||||
if (image.toUri().scheme == "content") {
|
if (image.scheme == "content") {
|
||||||
getApplication<PixelDroidApplication>().contentResolver.query(
|
applicationContext.contentResolver.query(
|
||||||
image.toUri(),
|
image,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -203,7 +220,7 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
cursor.getLong(sizeIndex)
|
cursor.getLong(sizeIndex)
|
||||||
} ?: 0
|
} ?: 0
|
||||||
} else {
|
} else {
|
||||||
image.toUri().toFile().length()
|
image.toFile().length()
|
||||||
}
|
}
|
||||||
|
|
||||||
val imagePart = ProgressRequestBody(inputStream, size, "image/*")
|
val imagePart = ProgressRequestBody(inputStream, size, "image/*")
|
||||||
|
@ -225,21 +242,32 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
var postSub: Disposable? = null
|
var postSub: Disposable? = null
|
||||||
|
|
||||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||||
val inter = api.updateProfilePicture(requestBody.parts[0])
|
|
||||||
|
val pixelfed = db.instanceDao().getActiveInstance().pixelfed
|
||||||
|
|
||||||
|
val inter =
|
||||||
|
if(pixelfed) api.updateProfilePicture(requestBody.parts[0])
|
||||||
|
else api.updateProfilePictureMastodon(requestBody.parts[0])
|
||||||
|
|
||||||
postSub = inter
|
postSub = inter
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ it: Account ->
|
/* onNext = */ { account: Account ->
|
||||||
Log.e("qsdfqsdfs", it.toString())
|
account.anyAvatar()?.let {
|
||||||
|
_uiState.update { currentUiState ->
|
||||||
|
currentUiState.copy(
|
||||||
|
profilePictureUri = it.toUri()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ e: Throwable ->
|
/* onError = */ { e: Throwable ->
|
||||||
|
Log.e("error", (e as? HttpException)?.message().orEmpty())
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
uploadProgress = 0,
|
uploadProgress = 0,
|
||||||
uploadingPicture = true,
|
uploadingPicture = false,
|
||||||
error = true
|
error = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -247,9 +275,10 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
postSub?.dispose()
|
postSub?.dispose()
|
||||||
sub.dispose()
|
sub.dispose()
|
||||||
},
|
},
|
||||||
{
|
/* onComplete = */ {
|
||||||
_uiState.update { currentUiState ->
|
_uiState.update { currentUiState ->
|
||||||
currentUiState.copy(
|
currentUiState.copy(
|
||||||
|
profilePictureChanged = false,
|
||||||
uploadProgress = 100,
|
uploadProgress = 100,
|
||||||
uploadingPicture = false
|
uploadingPicture = false
|
||||||
)
|
)
|
||||||
|
@ -265,7 +294,8 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||||
data class EditProfileActivityUiState(
|
data class EditProfileActivityUiState(
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val bio: String? = null,
|
val bio: String? = null,
|
||||||
val profilePictureUri: Uri?= null,
|
val profilePictureUri: Uri? = null,
|
||||||
|
val profilePictureChanged: Boolean = false,
|
||||||
val privateAccount: Boolean? = null,
|
val privateAccount: Boolean? = null,
|
||||||
val loadingProfile: Boolean = true,
|
val loadingProfile: Boolean = true,
|
||||||
val profileLoaded: Boolean = false,
|
val profileLoaded: Boolean = false,
|
||||||
|
@ -274,10 +304,4 @@ data class EditProfileActivityUiState(
|
||||||
val error: Boolean = false,
|
val error: Boolean = false,
|
||||||
val uploadingPicture: Boolean = false,
|
val uploadingPicture: Boolean = false,
|
||||||
val uploadProgress: Int = 0,
|
val uploadProgress: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
class EditProfileViewModelFactory(val application: Application) : ViewModelProvider.Factory {
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return modelClass.getConstructor(Application::class.java).newInstance(application)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,21 +6,28 @@ import android.text.method.LinkMovementMethod
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.databinding.ActivityProfileBinding
|
import org.pixeldroid.app.databinding.ActivityProfileBinding
|
||||||
|
import org.pixeldroid.app.posts.feeds.cachedFeeds.CachedFeedFragment
|
||||||
|
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedFeedFragment
|
||||||
import org.pixeldroid.app.posts.parseHTMLText
|
import org.pixeldroid.app.posts.parseHTMLText
|
||||||
import org.pixeldroid.app.utils.BaseActivity
|
import org.pixeldroid.app.utils.BaseActivity
|
||||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||||
import org.pixeldroid.app.utils.api.objects.Account
|
import org.pixeldroid.app.utils.api.objects.Account
|
||||||
|
import org.pixeldroid.app.utils.api.objects.FeedContent
|
||||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||||
|
import org.pixeldroid.app.utils.db.updateUserInfoDb
|
||||||
import org.pixeldroid.app.utils.setProfileImageFromURL
|
import org.pixeldroid.app.utils.setProfileImageFromURL
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -54,9 +61,32 @@ class ProfileActivity : BaseActivity() {
|
||||||
val tabs = createProfileTabs(account)
|
val tabs = createProfileTabs(account)
|
||||||
setupTabs(tabs)
|
setupTabs(tabs)
|
||||||
setContent(account)
|
setContent(account)
|
||||||
|
|
||||||
|
binding.profileMotion.setTransitionListener(
|
||||||
|
object : MotionLayout.TransitionListener {
|
||||||
|
override fun onTransitionStarted(
|
||||||
|
motionLayout: MotionLayout?, startId: Int, endId: Int,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {}
|
||||||
|
|
||||||
|
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
|
||||||
|
if (currentId == R.id.hideProfile && motionLayout?.startState == R.id.start) {
|
||||||
|
// If the 1st transition has been made go to the second one
|
||||||
|
motionLayout.setTransition(R.id.second)
|
||||||
|
} else if(currentId == R.id.hideProfile && motionLayout?.startState == R.id.hideProfile){
|
||||||
|
motionLayout.setTransition(R.id.first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTransitionTrigger(
|
||||||
|
motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createProfileTabs(account: Account?): Array<Fragment>{
|
private fun createProfileTabs(account: Account?): Array<UncachedFeedFragment<FeedContent>> {
|
||||||
|
|
||||||
val profileFeedFragment = ProfileFeedFragment()
|
val profileFeedFragment = ProfileFeedFragment()
|
||||||
profileFeedFragment.arguments = Bundle().apply {
|
profileFeedFragment.arguments = Bundle().apply {
|
||||||
|
@ -80,7 +110,7 @@ class ProfileActivity : BaseActivity() {
|
||||||
putSerializable(ProfileFeedFragment.COLLECTIONS, true)
|
putSerializable(ProfileFeedFragment.COLLECTIONS, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val returnArray: Array<Fragment> = arrayOf(
|
val returnArray: Array<UncachedFeedFragment<FeedContent>> = arrayOf(
|
||||||
profileGridFragment,
|
profileGridFragment,
|
||||||
profileFeedFragment,
|
profileFeedFragment,
|
||||||
profileCollectionsFragment
|
profileCollectionsFragment
|
||||||
|
@ -100,7 +130,7 @@ class ProfileActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTabs(
|
private fun setupTabs(
|
||||||
tabs: Array<Fragment>
|
tabs: Array<UncachedFeedFragment<FeedContent>>,
|
||||||
){
|
){
|
||||||
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
|
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
@ -132,8 +162,15 @@ class ProfileActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
}
|
|
||||||
|
|
||||||
|
binding.profileTabs.addOnTabSelectedListener(object : OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) {}
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||||
|
tabs[tab.position].onTabReClicked()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun setContent(account: Account?) {
|
private fun setContent(account: Account?) {
|
||||||
if(account != null) {
|
if(account != null) {
|
||||||
|
@ -152,6 +189,9 @@ class ProfileActivity : BaseActivity() {
|
||||||
).show()
|
).show()
|
||||||
return@launchWhenResumed
|
return@launchWhenResumed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateUserInfoDb(db, myAccount)
|
||||||
|
|
||||||
setViews(myAccount)
|
setViews(myAccount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,9 +257,15 @@ class ProfileActivity : BaseActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val editResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == RESULT_OK) {
|
||||||
|
// Profile was edited, reload
|
||||||
|
setContent(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onClickEditButton() {
|
private fun onClickEditButton() {
|
||||||
val intent = Intent(this, EditProfileActivity::class.java)
|
editResult.launch(Intent(this, EditProfileActivity::class.java))
|
||||||
ContextCompat.startActivity(this, intent, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClickFollowers(account: Account?) {
|
private fun onClickFollowers(account: Account?) {
|
||||||
|
|
|
@ -101,7 +101,7 @@ class ProfileFeedFragment : UncachedFeedFragment<FeedContent>() {
|
||||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
|
||||||
if(grid || bookmarks || collections || addCollection) {
|
if(grid || bookmarks || collections || addCollection) {
|
||||||
binding.list.layoutManager = GridLayoutManager(context, 3)
|
binding?.list?.layoutManager = GridLayoutManager(context, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the view model
|
// Get the view model
|
||||||
|
@ -191,8 +191,11 @@ class ProfileFeedFragment : UncachedFeedFragment<FeedContent>() {
|
||||||
val url = "$domain/i/collections/create"
|
val url = "$domain/i/collections/create"
|
||||||
|
|
||||||
if(domain.isNullOrEmpty() || !requireContext().openUrl(url)) {
|
if(domain.isNullOrEmpty() || !requireContext().openUrl(url)) {
|
||||||
Snackbar.make(binding.root, getString(R.string.new_collection_link_failed),
|
binding?.let { binding ->
|
||||||
Snackbar.LENGTH_LONG).show()
|
Snackbar.make(
|
||||||
|
binding.root, getString(R.string.new_collection_link_failed),
|
||||||
|
Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,6 @@ import org.pixeldroid.app.databinding.ActivityStoriesBinding
|
||||||
import org.pixeldroid.app.posts.setTextViewFromISO8601
|
import org.pixeldroid.app.posts.setTextViewFromISO8601
|
||||||
import org.pixeldroid.app.utils.BaseActivity
|
import org.pixeldroid.app.utils.BaseActivity
|
||||||
import org.pixeldroid.app.utils.api.objects.Account
|
import org.pixeldroid.app.utils.api.objects.Account
|
||||||
import org.pixeldroid.app.utils.api.objects.Story
|
|
||||||
import org.pixeldroid.app.utils.api.objects.StoryCarousel
|
|
||||||
|
|
||||||
|
|
||||||
class StoriesActivity: BaseActivity() {
|
class StoriesActivity: BaseActivity() {
|
||||||
|
|
||||||
|
@ -37,12 +34,11 @@ class StoriesActivity: BaseActivity() {
|
||||||
const val STORY_CAROUSEL_USER_ID = "LaunchStoryUserId"
|
const val STORY_CAROUSEL_USER_ID = "LaunchStoryUserId"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityStoriesBinding
|
private lateinit var binding: ActivityStoriesBinding
|
||||||
|
|
||||||
private lateinit var storyProgress: StoryProgress
|
private lateinit var storyProgress: StoryProgress
|
||||||
|
|
||||||
private lateinit var model: StoriesViewModel
|
private val model: StoriesViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
//force night mode always
|
//force night mode always
|
||||||
|
@ -50,18 +46,9 @@ class StoriesActivity: BaseActivity() {
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val carousel = intent.getSerializableExtra(STORY_CAROUSEL) as? StoryCarousel
|
|
||||||
val userId = intent.getStringExtra(STORY_CAROUSEL_USER_ID)
|
|
||||||
val selfCarousel: Array<Story>? = intent.getSerializableExtra(STORY_CAROUSEL_SELF) as? Array<Story>
|
|
||||||
|
|
||||||
binding = ActivityStoriesBinding.inflate(layoutInflater)
|
binding = ActivityStoriesBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
val _model: StoriesViewModel by viewModels {
|
|
||||||
StoriesViewModelFactory(application, carousel, userId, selfCarousel?.asList())
|
|
||||||
}
|
|
||||||
model = _model
|
|
||||||
|
|
||||||
storyProgress = StoryProgress(model.uiState.value.imageList.size)
|
storyProgress = StoryProgress(model.uiState.value.imageList.size)
|
||||||
binding.storyProgressImage.setImageDrawable(storyProgress)
|
binding.storyProgressImage.setImageDrawable(storyProgress)
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
package org.pixeldroid.app.stories
|
package org.pixeldroid.app.stories
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
|
||||||
import org.pixeldroid.app.utils.api.objects.CarouselUserContainer
|
import org.pixeldroid.app.utils.api.objects.CarouselUserContainer
|
||||||
import org.pixeldroid.app.utils.api.objects.Story
|
import org.pixeldroid.app.utils.api.objects.Story
|
||||||
import org.pixeldroid.app.utils.api.objects.StoryCarousel
|
import org.pixeldroid.app.utils.api.objects.StoryCarousel
|
||||||
|
@ -37,18 +35,13 @@ data class StoriesUiState(
|
||||||
val snackBar: Int? = null,
|
val snackBar: Int? = null,
|
||||||
val reply: String = ""
|
val reply: String = ""
|
||||||
)
|
)
|
||||||
|
@HiltViewModel
|
||||||
class StoriesViewModel(
|
class StoriesViewModel @Inject constructor(state: SavedStateHandle,
|
||||||
application: Application,
|
db: AppDatabase,
|
||||||
val carousel: StoryCarousel?,
|
private val apiHolder: PixelfedAPIHolder) : ViewModel() {
|
||||||
userId: String?,
|
private val carousel: StoryCarousel? = state[StoriesActivity.STORY_CAROUSEL]
|
||||||
val selfCarousel: List<Story>?
|
private val userId: String? = state[StoriesActivity.STORY_CAROUSEL_USER_ID]
|
||||||
) : AndroidViewModel(application) {
|
private val selfCarousel: Array<Story>? = state[StoriesActivity.STORY_CAROUSEL_SELF]
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var apiHolder: PixelfedAPIHolder
|
|
||||||
@Inject
|
|
||||||
lateinit var db: AppDatabase
|
|
||||||
|
|
||||||
private var currentAccount: CarouselUserContainer?
|
private var currentAccount: CarouselUserContainer?
|
||||||
|
|
||||||
|
@ -61,10 +54,9 @@ class StoriesViewModel(
|
||||||
private var timer: CountDownTimer? = null
|
private var timer: CountDownTimer? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
(application as PixelDroidApplication).getAppComponent().inject(this)
|
|
||||||
currentAccount =
|
currentAccount =
|
||||||
if (selfCarousel != null) {
|
if (selfCarousel != null) {
|
||||||
db.userDao().getActiveUser()?.let { CarouselUserContainer(it, selfCarousel) }
|
db.userDao().getActiveUser()?.let { CarouselUserContainer(it, selfCarousel.toList()) }
|
||||||
} else carousel?.nodes?.firstOrNull { it?.user?.id == userId }
|
} else carousel?.nodes?.firstOrNull { it?.user?.id == userId }
|
||||||
|
|
||||||
_uiState = MutableStateFlow(newUiStateFromCurrentAccount())
|
_uiState = MutableStateFlow(newUiStateFromCurrentAccount())
|
||||||
|
@ -216,14 +208,3 @@ class StoriesViewModel(
|
||||||
fun currentProfileId(): String? = currentAccount?.user?.id
|
fun currentProfileId(): String? = currentAccount?.user?.id
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StoriesViewModelFactory(
|
|
||||||
val application: Application,
|
|
||||||
val carousel: StoryCarousel?,
|
|
||||||
val userId: String?,
|
|
||||||
val selfCarousel: List<Story>?
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return modelClass.getConstructor(Application::class.java, StoryCarousel::class.java, String::class.java, List::class.java).newInstance(application, carousel, userId, selfCarousel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package org.pixeldroid.app.utils
|
package org.pixeldroid.app.utils
|
||||||
|
|
||||||
import android.os.Bundle
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.pixeldroid.app.utils.db.AppDatabase
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
open class BaseActivity : org.pixeldroid.common.ThemedActivity() {
|
open class BaseActivity : org.pixeldroid.common.ThemedActivity() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -12,11 +13,6 @@ open class BaseActivity : org.pixeldroid.common.ThemedActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var apiHolder: PixelfedAPIHolder
|
lateinit var apiHolder: PixelfedAPIHolder
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
(this.application as PixelDroidApplication).getAppComponent().inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package org.pixeldroid.app.utils
|
package org.pixeldroid.app.utils
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.utils.db.AppDatabase
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||||
|
@ -12,6 +12,7 @@ import javax.inject.Inject
|
||||||
/**
|
/**
|
||||||
* Base Fragment, for dependency injection and other things common to a lot of the fragments
|
* Base Fragment, for dependency injection and other things common to a lot of the fragments
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
open class BaseFragment: Fragment() {
|
open class BaseFragment: Fragment() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -20,11 +21,6 @@ open class BaseFragment: Fragment() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var db: AppDatabase
|
lateinit var db: AppDatabase
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
(requireActivity().application as PixelDroidApplication).getAppComponent().inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val requestPermissionDownloadPic =
|
internal val requestPermissionDownloadPic =
|
||||||
registerForActivityResult(
|
registerForActivityResult(
|
||||||
ActivityResultContracts.RequestPermission()
|
ActivityResultContracts.RequestPermission()
|
||||||
|
|
|
@ -3,14 +3,12 @@ package org.pixeldroid.app.utils
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import org.ligi.tracedroid.TraceDroid
|
import org.ligi.tracedroid.TraceDroid
|
||||||
import org.pixeldroid.app.utils.di.*
|
|
||||||
|
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
class PixelDroidApplication: Application() {
|
class PixelDroidApplication: Application() {
|
||||||
|
|
||||||
private lateinit var mApplicationComponent: ApplicationComponent
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
@ -19,18 +17,7 @@ class PixelDroidApplication: Application() {
|
||||||
val sharedPreferences =
|
val sharedPreferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(this)
|
PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
setThemeFromPreferences(sharedPreferences, resources)
|
setThemeFromPreferences(sharedPreferences, resources)
|
||||||
mApplicationComponent = DaggerApplicationComponent
|
|
||||||
.builder()
|
|
||||||
.applicationModule(ApplicationModule(this))
|
|
||||||
.databaseModule(DatabaseModule(applicationContext))
|
|
||||||
.aPIModule(APIModule())
|
|
||||||
.build()
|
|
||||||
mApplicationComponent.inject(this)
|
|
||||||
|
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAppComponent(): ApplicationComponent {
|
|
||||||
return mApplicationComponent
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,28 +1,25 @@
|
||||||
package org.pixeldroid.app.utils
|
package org.pixeldroid.app.utils
|
||||||
|
|
||||||
import android.content.*
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.ImageDecoder
|
|
||||||
import android.graphics.Matrix
|
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StyleRes
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.exifinterface.media.ExifInterface
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
|
@ -34,7 +31,7 @@ import okhttp3.HttpUrl
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
|
@ -338,18 +338,31 @@ interface PixelfedAPI {
|
||||||
@Header("Authorization") authorization: String? = null
|
@Header("Authorization") authorization: String? = null
|
||||||
): Account
|
): Account
|
||||||
|
|
||||||
//@Multipart
|
|
||||||
@PATCH("/api/v1/accounts/update_credentials")
|
@PATCH("/api/v1/accounts/update_credentials")
|
||||||
suspend fun updateCredentials(
|
suspend fun updateCredentials(
|
||||||
@Query(value = "display_name") displayName: String?,
|
@Query(value = "display_name") displayName: String?,
|
||||||
@Query(value = "note") note: String?,
|
@Query(value = "note") note: String?,
|
||||||
@Query(value = "locked") locked: Boolean?,
|
@Query(value = "locked") locked: Boolean?,
|
||||||
// @Part avatar: MultipartBody.Part?,
|
|
||||||
): Account
|
): Account
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pixelfed uses PHP, multipart uploads don't work through PATCH so we use POST as suggested
|
||||||
|
* here: https://github.com/pixelfed/pixelfed/issues/4250
|
||||||
|
* However, changing to POST breaks the upload on Mastodon.
|
||||||
|
*
|
||||||
|
* To have this work on Pixelfed and Mastodon without special logic to distinguish the two,
|
||||||
|
* we'll have to wait for PHP 8.4 and https://wiki.php.net/rfc/rfc1867-non-post
|
||||||
|
* which should come out end of 2024
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST("/api/v1/accounts/update_credentials")
|
||||||
|
fun updateProfilePicture(
|
||||||
|
@Part avatar: MultipartBody.Part?
|
||||||
|
): Observable<Account>
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
@PATCH("/api/v1/accounts/update_credentials")
|
@PATCH("/api/v1/accounts/update_credentials")
|
||||||
fun updateProfilePicture(
|
fun updateProfilePictureMastodon(
|
||||||
@Part avatar: MultipartBody.Part?
|
@Part avatar: MultipartBody.Part?
|
||||||
): Observable<Account>
|
): Observable<Account>
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.pixeldroid.app.utils.api.objects.Notification
|
||||||
PublicFeedStatusDatabaseEntity::class,
|
PublicFeedStatusDatabaseEntity::class,
|
||||||
Notification::class
|
Notification::class
|
||||||
],
|
],
|
||||||
version = 5
|
version = 6
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
@ -44,4 +44,9 @@ val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE instances ADD COLUMN videoEnabled INTEGER NOT NULL DEFAULT 1")
|
database.execSQL("ALTER TABLE instances ADD COLUMN videoEnabled INTEGER NOT NULL DEFAULT 1")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
val MIGRATION_5_6 = object : Migration(5, 6) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("ALTER TABLE instances ADD COLUMN pixelfed INTEGER NOT NULL DEFAULT 1")
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,41 +13,58 @@ import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity.Companion.DEF
|
||||||
import org.pixeldroid.app.utils.normalizeDomain
|
import org.pixeldroid.app.utils.normalizeDomain
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
suspend fun addUser(
|
||||||
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
|
db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
||||||
|
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String,
|
||||||
|
) {
|
||||||
db.userDao().insertOrUpdate(
|
db.userDao().insertOrUpdate(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = account.id!!,
|
user_id = account.id!!,
|
||||||
instance_uri = normalizeDomain(instance_uri),
|
instance_uri = normalizeDomain(instance_uri),
|
||||||
username = account.username!!,
|
username = account.username!!,
|
||||||
display_name = account.getDisplayName(),
|
display_name = account.getDisplayName(),
|
||||||
avatar_static = account.anyAvatar().orEmpty(),
|
avatar_static = account.anyAvatar().orEmpty(),
|
||||||
isActive = activeUser,
|
isActive = activeUser,
|
||||||
accessToken = accessToken,
|
accessToken = accessToken,
|
||||||
refreshToken = refreshToken,
|
refreshToken = refreshToken,
|
||||||
clientId = clientId,
|
clientId = clientId,
|
||||||
clientSecret = clientSecret
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) {
|
suspend fun updateUserInfoDb(db: AppDatabase, account: Account) {
|
||||||
|
val user = db.userDao().getActiveUser()!!
|
||||||
|
db.userDao().updateUserAccountDetails(
|
||||||
|
account.username.orEmpty(),
|
||||||
|
account.display_name.orEmpty(),
|
||||||
|
account.anyAvatar().orEmpty(),
|
||||||
|
user.user_id,
|
||||||
|
user.instance_uri
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) {
|
||||||
val dbInstance: InstanceDatabaseEntity = nodeInfo?.run {
|
val dbInstance: InstanceDatabaseEntity = nodeInfo?.run {
|
||||||
InstanceDatabaseEntity(
|
InstanceDatabaseEntity(
|
||||||
uri = normalizeDomain(metadata?.config?.site?.url!!),
|
uri = normalizeDomain(metadata?.config?.site?.url!!),
|
||||||
title = metadata.config.site.name!!,
|
title = metadata.config.site.name!!,
|
||||||
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
|
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
|
||||||
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_PHOTO_SIZE,
|
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull()
|
||||||
|
?: DEFAULT_MAX_PHOTO_SIZE,
|
||||||
// Pixelfed doesn't distinguish between max photo and video size
|
// Pixelfed doesn't distinguish between max photo and video size
|
||||||
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_VIDEO_SIZE,
|
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull()
|
||||||
|
?: DEFAULT_MAX_VIDEO_SIZE,
|
||||||
albumLimit = metadata.config.uploader.album_limit?.toIntOrNull() ?: DEFAULT_ALBUM_LIMIT,
|
albumLimit = metadata.config.uploader.album_limit?.toIntOrNull() ?: DEFAULT_ALBUM_LIMIT,
|
||||||
videoEnabled = metadata.config.features?.video ?: DEFAULT_VIDEO_ENABLED
|
videoEnabled = metadata.config.features?.video ?: DEFAULT_VIDEO_ENABLED,
|
||||||
|
pixelfed = metadata.software?.repo?.contains("pixelfed", ignoreCase = true) == true
|
||||||
)
|
)
|
||||||
} ?: instance?.run {
|
} ?: instance?.run {
|
||||||
InstanceDatabaseEntity(
|
InstanceDatabaseEntity(
|
||||||
uri = normalizeDomain(uri.orEmpty()),
|
uri = normalizeDomain(uri.orEmpty()),
|
||||||
title = title.orEmpty(),
|
title = title.orEmpty(),
|
||||||
maxStatusChars = max_toot_chars?.toInt() ?: DEFAULT_MAX_TOOT_CHARS,
|
maxStatusChars = max_toot_chars?.toInt() ?: DEFAULT_MAX_TOOT_CHARS,
|
||||||
|
pixelfed = false
|
||||||
)
|
)
|
||||||
} ?: throw IllegalArgumentException("Cannot store instance where both are null")
|
} ?: throw IllegalArgumentException("Cannot store instance where both are null")
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
package org.pixeldroid.app.utils.db.dao
|
package org.pixeldroid.app.utils.db.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface InstanceDao {
|
interface InstanceDao {
|
||||||
@Query("SELECT * FROM instances")
|
|
||||||
fun getAll(): List<InstanceDatabaseEntity>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM instances WHERE uri=:instanceUri")
|
@Query("SELECT * FROM instances WHERE uri=:instanceUri")
|
||||||
fun getInstance(instanceUri: String): InstanceDatabaseEntity
|
fun getInstance(instanceUri: String): InstanceDatabaseEntity
|
||||||
|
|
||||||
|
|
||||||
|
@Query("SELECT * FROM instances WHERE uri=(SELECT users.instance_uri FROM users WHERE isActive=1)")
|
||||||
|
fun getActiveInstance(): InstanceDatabaseEntity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert an instance, if it already exists return -1
|
* Insert an instance, if it already exists return -1
|
||||||
*/
|
*/
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun insertInstance(instance: InstanceDatabaseEntity): Long
|
suspend fun insertInstance(instance: InstanceDatabaseEntity): Long
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
fun updateInstance(instance: InstanceDatabaseEntity)
|
suspend fun updateInstance(instance: InstanceDatabaseEntity)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
fun insertOrUpdate(instance: InstanceDatabaseEntity) {
|
suspend fun insertOrUpdate(instance: InstanceDatabaseEntity) {
|
||||||
if (insertInstance(instance) == -1L) {
|
if (insertInstance(instance) == -1L) {
|
||||||
updateInstance(instance)
|
updateInstance(instance)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package org.pixeldroid.app.utils.db.dao
|
package org.pixeldroid.app.utils.db.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
@ -9,17 +15,21 @@ interface UserDao {
|
||||||
* Insert a user, if it already exists return -1
|
* Insert a user, if it already exists return -1
|
||||||
*/
|
*/
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun insertUser(user: UserDatabaseEntity): Long
|
suspend fun insertUser(user: UserDatabaseEntity): Long
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
fun insertOrUpdate(user: UserDatabaseEntity) {
|
suspend fun insertOrUpdate(user: UserDatabaseEntity) {
|
||||||
if (insertUser(user) == -1L) {
|
if (insertUser(user) == -1L) {
|
||||||
updateUser(user)
|
updateUser(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
fun updateUser(user: UserDatabaseEntity)
|
suspend fun updateUser(user: UserDatabaseEntity)
|
||||||
|
|
||||||
|
@Query("UPDATE users SET username = :username, display_name = :displayName, avatar_static = :avatarStatic WHERE user_id = :id and instance_uri = :instanceUri")
|
||||||
|
suspend fun updateUserAccountDetails(username: String, displayName: String, avatarStatic: String, id: String, instanceUri: String)
|
||||||
|
|
||||||
|
|
||||||
@Query("UPDATE users SET accessToken = :accessToken, refreshToken = :refreshToken WHERE user_id = :id and instance_uri = :instanceUri")
|
@Query("UPDATE users SET accessToken = :accessToken, refreshToken = :refreshToken WHERE user_id = :id and instance_uri = :instanceUri")
|
||||||
fun updateAccessToken(accessToken: String, refreshToken: String, id: String, instanceUri: String)
|
fun updateAccessToken(accessToken: String, refreshToken: String, id: String, instanceUri: String)
|
||||||
|
@ -27,6 +37,9 @@ interface UserDao {
|
||||||
@Query("SELECT * FROM users")
|
@Query("SELECT * FROM users")
|
||||||
fun getAll(): List<UserDatabaseEntity>
|
fun getAll(): List<UserDatabaseEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM users")
|
||||||
|
fun getAllFlow(): Flow<List<UserDatabaseEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM users WHERE isActive=1")
|
@Query("SELECT * FROM users WHERE isActive=1")
|
||||||
fun getActiveUser(): UserDatabaseEntity?
|
fun getActiveUser(): UserDatabaseEntity?
|
||||||
|
|
||||||
|
|
|
@ -4,20 +4,22 @@ import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
@Entity(tableName = "instances")
|
@Entity(tableName = "instances")
|
||||||
data class InstanceDatabaseEntity (
|
data class InstanceDatabaseEntity(
|
||||||
@PrimaryKey var uri: String,
|
@PrimaryKey var uri: String,
|
||||||
var title: String,
|
var title: String,
|
||||||
var maxStatusChars: Int = DEFAULT_MAX_TOOT_CHARS,
|
var maxStatusChars: Int = DEFAULT_MAX_TOOT_CHARS,
|
||||||
// Per-file file-size limit in KB. Defaults to 15000 (15MB). Default limit for Mastodon is 8MB
|
// Per-file file-size limit in KB. Defaults to 15000 (15MB). Default limit for Mastodon is 8MB
|
||||||
var maxPhotoSize: Int = DEFAULT_MAX_PHOTO_SIZE,
|
var maxPhotoSize: Int = DEFAULT_MAX_PHOTO_SIZE,
|
||||||
// Mastodon has different file limits for videos, default of 40MB
|
// Mastodon has different file limits for videos, default of 40MB
|
||||||
var maxVideoSize: Int = DEFAULT_MAX_VIDEO_SIZE,
|
var maxVideoSize: Int = DEFAULT_MAX_VIDEO_SIZE,
|
||||||
// How many photos can go into an album. Default limit for Pixelfed and Mastodon is 4
|
// How many photos can go into an album. Default limit for Pixelfed and Mastodon is 4
|
||||||
var albumLimit: Int = DEFAULT_ALBUM_LIMIT,
|
var albumLimit: Int = DEFAULT_ALBUM_LIMIT,
|
||||||
// Is video functionality enabled on this instance?
|
// Is video functionality enabled on this instance?
|
||||||
var videoEnabled: Boolean = DEFAULT_VIDEO_ENABLED,
|
var videoEnabled: Boolean = DEFAULT_VIDEO_ENABLED,
|
||||||
|
// Is this Pixelfed instance?
|
||||||
|
var pixelfed: Boolean = true,
|
||||||
) {
|
) {
|
||||||
companion object{
|
companion object {
|
||||||
// Default max number of chars for Mastodon: used when their is no other value supplied by
|
// Default max number of chars for Mastodon: used when their is no other value supplied by
|
||||||
// either NodeInfo or the instance endpoint
|
// either NodeInfo or the instance endpoint
|
||||||
const val DEFAULT_MAX_TOOT_CHARS = 500
|
const val DEFAULT_MAX_TOOT_CHARS = 500
|
||||||
|
|
|
@ -6,13 +6,16 @@ import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser
|
import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class APIModule{
|
@InstallIn(SingletonComponent::class)
|
||||||
|
class APIModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@ -54,7 +57,7 @@ class TokenAuthenticator(val user: UserDatabaseEntity, val db: AppDatabase, val
|
||||||
client_secret = user.clientSecret
|
client_secret = user.clientSecret
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}catch (e: Exception){
|
} catch (e: Exception){
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
package org.pixeldroid.app.utils.di
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import org.pixeldroid.app.utils.BaseActivity
|
|
||||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
|
||||||
import org.pixeldroid.app.utils.db.AppDatabase
|
|
||||||
import org.pixeldroid.app.utils.BaseFragment
|
|
||||||
import dagger.Component
|
|
||||||
import org.pixeldroid.app.postCreation.PostCreationViewModel
|
|
||||||
import org.pixeldroid.app.profile.EditProfileViewModel
|
|
||||||
import org.pixeldroid.app.stories.StoriesViewModel
|
|
||||||
import org.pixeldroid.app.stories.StoryCarouselViewHolder
|
|
||||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Component(modules = [ApplicationModule::class, DatabaseModule::class, APIModule::class])
|
|
||||||
interface ApplicationComponent {
|
|
||||||
fun inject(application: PixelDroidApplication?)
|
|
||||||
fun inject(activity: BaseActivity?)
|
|
||||||
fun inject(feedFragment: BaseFragment)
|
|
||||||
fun inject(notificationsWorker: NotificationsWorker)
|
|
||||||
fun inject(postCreationViewModel: PostCreationViewModel)
|
|
||||||
fun inject(editProfileViewModel: EditProfileViewModel)
|
|
||||||
fun inject(storiesViewModel: StoriesViewModel)
|
|
||||||
|
|
||||||
val context: Context?
|
|
||||||
val application: Application?
|
|
||||||
val database: AppDatabase
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package org.pixeldroid.app.utils.di
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
|
|
||||||
@Module
|
|
||||||
class ApplicationModule(app: Application) {
|
|
||||||
private val mApplication: Application = app
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideContext(): Context {
|
|
||||||
return mApplication
|
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideApplication(): Application {
|
|
||||||
return mApplication
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -5,20 +5,27 @@ import androidx.room.Room
|
||||||
import org.pixeldroid.app.utils.db.AppDatabase
|
import org.pixeldroid.app.utils.db.AppDatabase
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
import org.pixeldroid.app.utils.db.MIGRATION_3_4
|
import org.pixeldroid.app.utils.db.MIGRATION_3_4
|
||||||
import org.pixeldroid.app.utils.db.MIGRATION_4_5
|
import org.pixeldroid.app.utils.db.MIGRATION_4_5
|
||||||
|
import org.pixeldroid.app.utils.db.MIGRATION_5_6
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
@Module
|
@Module
|
||||||
class DatabaseModule(private val context: Context) {
|
class DatabaseModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesDatabase(): AppDatabase {
|
fun providesDatabase(
|
||||||
|
@ApplicationContext applicationContext: Context
|
||||||
|
): AppDatabase {
|
||||||
return Room.databaseBuilder(
|
return Room.databaseBuilder(
|
||||||
context,
|
applicationContext,
|
||||||
AppDatabase::class.java, "pixeldroid"
|
AppDatabase::class.java, "pixeldroid"
|
||||||
).addMigrations(MIGRATION_3_4).addMigrations(MIGRATION_4_5)
|
).addMigrations(MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
|
||||||
.allowMainThreadQueries().build()
|
.allowMainThreadQueries().build()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,9 +32,6 @@ import java.io.IOException
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationsWorker(
|
class NotificationsWorker(
|
||||||
context: Context,
|
context: Context,
|
||||||
params: WorkerParameters
|
params: WorkerParameters
|
||||||
|
@ -46,9 +43,6 @@ class NotificationsWorker(
|
||||||
lateinit var apiHolder: PixelfedAPIHolder
|
lateinit var apiHolder: PixelfedAPIHolder
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
|
|
||||||
(applicationContext as PixelDroidApplication).getAppComponent().inject(this)
|
|
||||||
|
|
||||||
val users: List<UserDatabaseEntity> = db.userDao().getAll()
|
val users: List<UserDatabaseEntity> = db.userDao().getAll()
|
||||||
|
|
||||||
for (user in users){
|
for (user in users){
|
||||||
|
@ -306,8 +300,7 @@ fun removeNotificationChannelsFromAccount(context: Context, user: UserDatabaseEn
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
notificationManager.deleteNotificationChannelGroup(channelGroupId.hashCode().toString())
|
notificationManager.deleteNotificationChannelGroup(channelGroupId.hashCode().toString())
|
||||||
} else {
|
} else {
|
||||||
val types: MutableList<Notification.NotificationType?> =
|
val types: MutableList<Notification.NotificationType?> = entries.toMutableList()
|
||||||
Notification.NotificationType.values().toMutableList()
|
|
||||||
types += null
|
types += null
|
||||||
|
|
||||||
types.forEach {
|
types.forEach {
|
||||||
|
|
|
@ -3,88 +3,104 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/scrollview"
|
android:id="@+id/scrollview"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSecondaryContainer"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".posts.PostActivity">
|
tools:context=".posts.PostActivity">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/app_bar_layout"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:background="?attr/colorSecondaryContainer"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/top_bar"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:background="?attr/colorSurface">
|
||||||
app:layout_scrollFlags="scroll|enterAlways"/>
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
<androidx.core.widget.NestedScrollView
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:id="@+id/collapsing_toolbar_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
<include
|
app:layout_constraintBottom_toTopOf="parent"
|
||||||
android:id="@+id/postFragmentSingle"
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
layout="@layout/post_fragment" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/commentIn"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
app:layout_constraintTop_toBottomOf="@+id/postFragmentSingle"
|
|
||||||
tools:layout_editor_absoluteX="10dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/textInputLayout2"
|
android:id="@+id/top_bar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:background="?attr/colorSecondaryContainer"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/submitComment"
|
android:minHeight="?attr/actionBarSize"
|
||||||
app:layout_constraintStart_toStartOf="parent">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/editComment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/comment"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="text|textCapSentences|textMultiLine" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/submitComment"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:contentDescription="@string/submit_comment"
|
|
||||||
android:text="@string/comment"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/textInputLayout2"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/textInputLayout2"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/textInputLayout2" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/constraintPost"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_bar">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/postFragmentSingle"
|
||||||
|
layout="@layout/post_fragment" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/commentIn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/constraintPost"
|
||||||
|
tools:layout_editor_absoluteX="10dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/textInputLayout2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/submitComment"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/editComment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/comment_noun"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="text|textCapSentences|textMultiLine" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/submitComment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:contentDescription="@string/submit_comment"
|
||||||
|
android:text="@string/comment_verb"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/textInputLayout2"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/textInputLayout2"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/textInputLayout2" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/commentFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="500dp"
|
||||||
|
android:fillViewport="true"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/commentIn" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
|
||||||
android:id="@+id/commentFragment"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
|
@ -3,154 +3,155 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".profile.ProfileActivity">
|
tools:context=".profile.ProfileActivity">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
android:id="@+id/app_bar_layout"
|
android:id="@+id/profileMotion"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:background="?attr/colorSecondaryContainer"
|
||||||
|
app:layoutDescription="@xml/collapsing_motion_layout_scene">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/top_bar"
|
||||||
android:background="?attr/colorSecondaryContainer"
|
android:background="?attr/colorSecondaryContainer"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:fitsSystemWindows="true"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?attr/actionBarSize"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/top_bar"
|
android:id="@+id/profile"
|
||||||
android:layout_width="match_parent"
|
android:elevation="-1dp"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_bar">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/profilePictureImageView"
|
||||||
|
android:layout_width="88dp"
|
||||||
|
android:layout_height="88dp"
|
||||||
|
android:clickable="false"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:contentDescription="@string/profile_picture"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:srcCompat="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nbPostsTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:layout_marginStart="20dp"
|
||||||
app:layout_scrollFlags="scroll|enterAlways"/>
|
android:layout_marginTop="10dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:text="@string/default_nposts"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/descriptionTextView" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
<TextView
|
||||||
android:id="@+id/collapsing_toolbar_layout"
|
android:id="@+id/nbFollowersTextView"
|
||||||
android:background="?attr/colorSurface"
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/default_nfollowers"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/nbPostsTextView"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/nbFollowingTextView"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/nbPostsTextView"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/nbPostsTextView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nbFollowingTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/default_nfollowing"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/nbFollowersTextView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/nbFollowersTextView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/accountNameTextView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:clickable="false"
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:text="@string/no_username"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/profilePictureImageView" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/descriptionTextView"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginBottom="12dp"
|
android:clickable="false"
|
||||||
android:visibility="visible"
|
android:layout_height="wrap_content"
|
||||||
app:layout_collapseMode="parallax"
|
android:layout_marginLeft="20dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/nbFollowersTextView"
|
android:layout_marginTop="5dp"
|
||||||
tools:visibility="visible">
|
android:layout_marginRight="20dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/accountNameTextView" />
|
||||||
|
|
||||||
<ImageView
|
<Button
|
||||||
android:id="@+id/profilePictureImageView"
|
android:id="@+id/followButton"
|
||||||
android:layout_width="88dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="88dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="20dp"
|
android:layout_marginEnd="20dp"
|
||||||
android:layout_marginTop="6dp"
|
android:text="@string/follow"
|
||||||
android:contentDescription="@string/profile_picture"
|
android:visibility="invisible"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
tools:srcCompat="@tools:sample/avatars" />
|
app:layout_constraintTop_toTopOf="@+id/profilePictureImageView" />
|
||||||
|
|
||||||
<TextView
|
<Button
|
||||||
android:id="@+id/nbPostsTextView"
|
android:id="@+id/editButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="20dp"
|
android:text="@string/edit_profile"
|
||||||
android:layout_marginTop="10dp"
|
android:visibility="gone"
|
||||||
android:gravity="center"
|
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
|
||||||
android:text="@string/default_nposts"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:textStyle="bold"
|
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintTop_toTopOf="@+id/profilePictureImageView" />
|
||||||
app:layout_constraintTop_toBottomOf="@+id/descriptionTextView" />
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/nbFollowersTextView"
|
android:id="@+id/profileTabs"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:gravity="center"
|
app:layout_constraintTop_toBottomOf="@id/profile"
|
||||||
android:text="@string/default_nfollowers"
|
android:layout_height="wrap_content"
|
||||||
android:textStyle="bold"
|
app:layout_scrollFlags="scroll|enterAlways" />
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/nbPostsTextView"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/nbFollowingTextView"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/nbPostsTextView"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/nbPostsTextView" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/nbFollowingTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/default_nfollowing"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/nbFollowersTextView"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/nbFollowersTextView" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/accountNameTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:text="@string/no_username"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/profilePictureImageView" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/descriptionTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="20dp"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginRight="20dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/accountNameTextView" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/followButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:text="@string/follow"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/profilePictureImageView" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/editButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/edit_profile"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/profilePictureImageView" />
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/profileTabs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/nbPostsTextView"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/view_pager"
|
android:id="@+id/view_pager"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_constraintTop_toBottomOf="@id/profileTabs"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -16,6 +16,8 @@
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/top_bar"
|
android:id="@+id/top_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
app:titleTextColor="?attr/colorOnSecondaryContainer"
|
||||||
|
android:background="?attr/colorSecondaryContainer"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -38,7 +40,6 @@
|
||||||
android:id="@+id/refreshLayout"
|
android:id="@+id/refreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -8,14 +8,6 @@
|
||||||
android:layout_marginBottom="5dp"
|
android:layout_marginBottom="5dp"
|
||||||
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto">
|
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:clipToPadding="false">
|
|
||||||
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/profilePic"
|
android:id="@+id/profilePic"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
|
@ -243,6 +235,4 @@
|
||||||
app:layout_constraintTop_toBottomOf="@+id/postDate"
|
app:layout_constraintTop_toBottomOf="@+id/postDate"
|
||||||
tools:text="3 comments" />
|
tools:text="3 comments" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
|
|
@ -48,7 +48,7 @@
|
||||||
<string name="loading_toast">حدث خلل اثناء التحميل</string>
|
<string name="loading_toast">حدث خلل اثناء التحميل</string>
|
||||||
<string name="upload_post_error">فشل في تحميل المنشور</string>
|
<string name="upload_post_error">فشل في تحميل المنشور</string>
|
||||||
|
|
||||||
<string name="comment">تعليق</string>
|
<string name="comment_verb">تعليق</string>
|
||||||
<string name="comment_posted">التعليق: تم نشر%1$s!</string>
|
<string name="comment_posted">التعليق: تم نشر%1$s!</string>
|
||||||
<string name="comment_error">خطأ في التعليق!</string>
|
<string name="comment_error">خطأ في التعليق!</string>
|
||||||
<string name="share_image">مشاركة الصورة</string>
|
<string name="share_image">مشاركة الصورة</string>
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
<string name="follow_error">No s\'ha pogut seguir</string>
|
<string name="follow_error">No s\'ha pogut seguir</string>
|
||||||
<string name="follow_button_failed">No s\'ha pogut mostrar el botó de seguir</string>
|
<string name="follow_button_failed">No s\'ha pogut mostrar el botó de seguir</string>
|
||||||
<string name="follow_status_failed">No s\'ha pogut obtenir l\'estat de seguiment</string>
|
<string name="follow_status_failed">No s\'ha pogut obtenir l\'estat de seguiment</string>
|
||||||
<string name="comment">Comentari</string>
|
<string name="comment_verb">Comentari</string>
|
||||||
<string name="comment_posted">Comentari: %1$s publicat!</string>
|
<string name="comment_posted">Comentari: %1$s publicat!</string>
|
||||||
<string name="comment_error">Error de comentari!</string>
|
<string name="comment_error">Error de comentari!</string>
|
||||||
<string name="share_image">Compartir imatge</string>
|
<string name="share_image">Compartir imatge</string>
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
<string name="share_image">Sdílet obrázek</string>
|
<string name="share_image">Sdílet obrázek</string>
|
||||||
<string name="comment_error">Chyba komentáře!</string>
|
<string name="comment_error">Chyba komentáře!</string>
|
||||||
<string name="comment_posted">Komentář: %1$s zveřejněn!</string>
|
<string name="comment_posted">Komentář: %1$s zveřejněn!</string>
|
||||||
<string name="comment">Komentář</string>
|
<string name="comment_verb">Komentář</string>
|
||||||
<plurals name="number_comments">
|
<plurals name="number_comments">
|
||||||
<item quantity="one">%d komentář</item>
|
<item quantity="one">%d komentář</item>
|
||||||
<item quantity="few">%d komentáře</item>
|
<item quantity="few">%d komentáře</item>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<string name="add_account_description">Weiteres Pixelfed-Konto hinzufügen</string>
|
<string name="add_account_description">Weiteres Pixelfed-Konto hinzufügen</string>
|
||||||
<string name="light_theme">Hell</string>
|
<string name="light_theme">Hell</string>
|
||||||
<string name="dark_theme">Dunkel</string>
|
<string name="dark_theme">Dunkel</string>
|
||||||
<string name="comment">Kommentieren</string>
|
<string name="comment_verb">Kommentieren</string>
|
||||||
<string name="share_image">Bild teilen</string>
|
<string name="share_image">Bild teilen</string>
|
||||||
<string name="empty_comment">Der Kommentar darf nicht leer sein!</string>
|
<string name="empty_comment">Der Kommentar darf nicht leer sein!</string>
|
||||||
<string name="no_description">Keine Beschreibung</string>
|
<string name="no_description">Keine Beschreibung</string>
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
<string name="follow_error">No pudo seguir</string>
|
<string name="follow_error">No pudo seguir</string>
|
||||||
<string name="follow_button_failed">No se pudo mostrar el botón de seguimiento</string>
|
<string name="follow_button_failed">No se pudo mostrar el botón de seguimiento</string>
|
||||||
<string name="follow_status_failed">No se pudo obtener el estado de seguidores</string>
|
<string name="follow_status_failed">No se pudo obtener el estado de seguidores</string>
|
||||||
<string name="comment">Comentar</string>
|
<string name="comment_verb">Comentar</string>
|
||||||
<string name="comment_posted">¡Comentario: %1$s publicado!</string>
|
<string name="comment_posted">¡Comentario: %1$s publicado!</string>
|
||||||
<string name="comment_error">¡Error al comentar!</string>
|
<string name="comment_error">¡Error al comentar!</string>
|
||||||
<string name="share_image">Compartir imagen</string>
|
<string name="share_image">Compartir imagen</string>
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
<string name="follow_error">Ezin izan da jarraitu</string>
|
<string name="follow_error">Ezin izan da jarraitu</string>
|
||||||
<string name="follow_button_failed">Ezin izan da jarraipen-botoia erakutsi</string>
|
<string name="follow_button_failed">Ezin izan da jarraipen-botoia erakutsi</string>
|
||||||
<string name="follow_status_failed">Ezin izan da segimendu-egoera lortu</string>
|
<string name="follow_status_failed">Ezin izan da segimendu-egoera lortu</string>
|
||||||
<string name="comment">Iruzkin</string>
|
<string name="comment_verb">Iruzkin</string>
|
||||||
<string name="posted_on">%1$s(e)n argitaratua</string>
|
<string name="posted_on">%1$s(e)n argitaratua</string>
|
||||||
<string name="hashtags">TRAOLAK</string>
|
<string name="hashtags">TRAOLAK</string>
|
||||||
<string name="accounts">KONTUAK</string>
|
<string name="accounts">KONTUAK</string>
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
<string name="share_image">همرسانی تصویر</string>
|
<string name="share_image">همرسانی تصویر</string>
|
||||||
<string name="comment_error">خطا در درج نظر!</string>
|
<string name="comment_error">خطا در درج نظر!</string>
|
||||||
<string name="comment_posted">نظر: %1$s منتشر کرد!</string>
|
<string name="comment_posted">نظر: %1$s منتشر کرد!</string>
|
||||||
<string name="comment">نظر</string>
|
<string name="comment_verb">نظر</string>
|
||||||
<string name="follow_status_failed">نتوانستیم وضعیت پیگیری را دریافت کنیم</string>
|
<string name="follow_status_failed">نتوانستیم وضعیت پیگیری را دریافت کنیم</string>
|
||||||
<string name="follow_button_failed">نتوانستیم دکمه پیگیری را نمایش دهیم</string>
|
<string name="follow_button_failed">نتوانستیم دکمه پیگیری را نمایش دهیم</string>
|
||||||
<string name="follow_error">نتوانستیم پیگیری کنیم</string>
|
<string name="follow_error">نتوانستیم پیگیری کنیم</string>
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
<string name="search">Hae</string>
|
<string name="search">Hae</string>
|
||||||
<string name="unfollow">Seurataan</string>
|
<string name="unfollow">Seurataan</string>
|
||||||
<string name="follow">Seuraa</string>
|
<string name="follow">Seuraa</string>
|
||||||
<string name="comment">Kommentti</string>
|
<string name="comment_verb">Kommentti</string>
|
||||||
<string name="image_download_downloading">Ladataan…</string>
|
<string name="image_download_downloading">Ladataan…</string>
|
||||||
<string name="gallery_button_alt">Galleria</string>
|
<string name="gallery_button_alt">Galleria</string>
|
||||||
<string name="edit">Muokkaa</string>
|
<string name="edit">Muokkaa</string>
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
<string name="share_image">Partager image</string>
|
<string name="share_image">Partager image</string>
|
||||||
<string name="comment_error">Erreur de commentaire !</string>
|
<string name="comment_error">Erreur de commentaire !</string>
|
||||||
<string name="comment_posted">Commentaire : %1$s publié !</string>
|
<string name="comment_posted">Commentaire : %1$s publié !</string>
|
||||||
<string name="comment">Commenter</string>
|
<string name="comment_verb">Commenter</string>
|
||||||
<string name="follow_status_failed">Impossible d\'obtenir l\'état de suivi</string>
|
<string name="follow_status_failed">Impossible d\'obtenir l\'état de suivi</string>
|
||||||
<string name="follow_button_failed">Impossible d\'afficher le bouton de suivi</string>
|
<string name="follow_button_failed">Impossible d\'afficher le bouton de suivi</string>
|
||||||
<string name="follow_error">Impossible de suivre</string>
|
<string name="follow_error">Impossible de suivre</string>
|
||||||
|
@ -138,35 +138,45 @@
|
||||||
<plurals name="nb_following">
|
<plurals name="nb_following">
|
||||||
<item quantity="one">%d
|
<item quantity="one">%d
|
||||||
\nAbonnement</item>
|
\nAbonnement</item>
|
||||||
|
<item quantity="many">%d
|
||||||
|
\nAbonnements</item>
|
||||||
<item quantity="other">%d
|
<item quantity="other">%d
|
||||||
\nAbonnements</item>
|
\nAbonnements</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="nb_followers">
|
<plurals name="nb_followers">
|
||||||
<item quantity="one">%d
|
<item quantity="one">%d
|
||||||
\nAbonné·e</item>
|
\nAbonné·e</item>
|
||||||
|
<item quantity="many">%d
|
||||||
|
\nAbonné·e·s</item>
|
||||||
<item quantity="other">%d
|
<item quantity="other">%d
|
||||||
\nAbonné·e·s</item>
|
\nAbonné·e·s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="nb_posts">
|
<plurals name="nb_posts">
|
||||||
<item quantity="one">%d
|
<item quantity="one">%d
|
||||||
\nPublication</item>
|
\nPublication</item>
|
||||||
|
<item quantity="many">%d
|
||||||
|
\nPublications</item>
|
||||||
<item quantity="other">%d
|
<item quantity="other">%d
|
||||||
\nPublications</item>
|
\nPublications</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="number_comments">
|
<plurals name="number_comments">
|
||||||
<item quantity="one">%d commentaire</item>
|
<item quantity="one">%d commentaire</item>
|
||||||
|
<item quantity="many">%d commentaires</item>
|
||||||
<item quantity="other">%d commentaires</item>
|
<item quantity="other">%d commentaires</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="shares">
|
<plurals name="shares">
|
||||||
<item quantity="one">%d Partage</item>
|
<item quantity="one">%d Partage</item>
|
||||||
|
<item quantity="many">%d Partages</item>
|
||||||
<item quantity="other">%d Partages</item>
|
<item quantity="other">%d Partages</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="likes">
|
<plurals name="likes">
|
||||||
<item quantity="one">%d J\'aime</item>
|
<item quantity="one">%d J\'aime</item>
|
||||||
|
<item quantity="many">%d J\'aime</item>
|
||||||
<item quantity="other">%d J\'aime</item>
|
<item quantity="other">%d J\'aime</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="description_max_characters">
|
<plurals name="description_max_characters">
|
||||||
<item quantity="one">La description doit contenir au moins %d lettre.</item>
|
<item quantity="one">La description doit contenir au moins %d lettre.</item>
|
||||||
|
<item quantity="many">La description doit contenir au moins %d lettres.</item>
|
||||||
<item quantity="other">La description doit contenir au moins %d lettres.</item>
|
<item quantity="other">La description doit contenir au moins %d lettres.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="delete_post_failed_error">Impossible de supprimer la publication, erreur %1$d</string>
|
<string name="delete_post_failed_error">Impossible de supprimer la publication, erreur %1$d</string>
|
||||||
|
@ -184,14 +194,14 @@
|
||||||
<string name="followed_notification_channel">Nouveaux·elles abonné·e·s</string>
|
<string name="followed_notification_channel">Nouveaux·elles abonné·e·s</string>
|
||||||
<string name="mention_notification_channel">Mentions</string>
|
<string name="mention_notification_channel">Mentions</string>
|
||||||
<string name="shared_notification_channel">Partages</string>
|
<string name="shared_notification_channel">Partages</string>
|
||||||
<string name="liked_notification_channel">Favoris</string>
|
<string name="liked_notification_channel">J\'aime</string>
|
||||||
<string name="comment_notification_channel">Commentaires</string>
|
<string name="comment_notification_channel">Commentaires</string>
|
||||||
<string name="poll_notification_channel">Sondages</string>
|
<string name="poll_notification_channel">Sondages</string>
|
||||||
<string name="other_notification_channel">Autre</string>
|
<string name="other_notification_channel">Autre</string>
|
||||||
<string name="file_not_found">Le fichier %1$s n’a pas été trouvé</string>
|
<string name="file_not_found">Le fichier %1$s n’a pas été trouvé</string>
|
||||||
<string name="notifications_settings">Paramètres des notifications</string>
|
<string name="notifications_settings">Paramètres des notifications</string>
|
||||||
<string name="post_is_video">Cette publication est une vidéo</string>
|
<string name="post_is_video">Cette publication est une vidéo</string>
|
||||||
<string name="dialog_message_cancel_follow_request">Annuler la demande d’abonnement \?</string>
|
<string name="dialog_message_cancel_follow_request">Annuler la demande de suivi ?</string>
|
||||||
<string name="unbookmark">Retirer des signets</string>
|
<string name="unbookmark">Retirer des signets</string>
|
||||||
<string name="delete_collection">Supprimer la collection</string>
|
<string name="delete_collection">Supprimer la collection</string>
|
||||||
<string name="your_name">Votre nom</string>
|
<string name="your_name">Votre nom</string>
|
||||||
|
@ -223,4 +233,85 @@
|
||||||
<string name="profile_saved">Modifications enregistrées !</string>
|
<string name="profile_saved">Modifications enregistrées !</string>
|
||||||
<string name="change_profile_picture">Changer votre image de profil</string>
|
<string name="change_profile_picture">Changer votre image de profil</string>
|
||||||
<string name="switch_accounts">Permutation de comptes</string>
|
<string name="switch_accounts">Permutation de comptes</string>
|
||||||
|
<string name="edit_link_failed">Impossible d\'ouvrir la page d\'édition</string>
|
||||||
|
<string name="follow_requested">Abonnement demandé</string>
|
||||||
|
<string name="comment_noun">Commentaire</string>
|
||||||
|
<string name="redraft_dialog_launch">La reformulation de cet article vous permettra de modifier la photo et sa description, mais supprimera tous les commentaires et les mentions \"J\'aime\". Poursuivre ?</string>
|
||||||
|
<string name="notification_thumbnail">Vignette de l\'image dans ce message</string>
|
||||||
|
<string name="always_show_nsfw">Toujours afficher les contenus sensibles</string>
|
||||||
|
<string name="post_preview">Aperçu d\'un message</string>
|
||||||
|
<plurals name="notification_title_summary">
|
||||||
|
<item quantity="one">%d nouvelle notification</item>
|
||||||
|
<item quantity="many">%d nouvelles notifications</item>
|
||||||
|
<item quantity="other">%d nouvelles notifications</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="items_load_success">
|
||||||
|
<item quantity="one">%d article chargé avec succès</item>
|
||||||
|
<item quantity="many">%d articles chargés avec succès</item>
|
||||||
|
<item quantity="other">%d articles chargés avec succès</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="notification_summary_large">%1$s, %2$s, %3$s et %4$d autres</string>
|
||||||
|
<string name="notification_summary_medium">%1$s, %2$s, et %3$s</string>
|
||||||
|
<string name="video_not_supported">Le serveur que vous utilisez ne prend pas en charge les téléchargements de vidéos, il se peut que vous ne puissiez pas télécharger les vidéos incluses dans cet article</string>
|
||||||
|
<string name="new_collection_link_failed">Échec de l\'ouverture de la page de création d\'une collection</string>
|
||||||
|
<string name="bookmark">Favori</string>
|
||||||
|
<string name="unknown_error_in_error">Erreur inconnue, vérifiez si le serveur est en panne : %1$s</string>
|
||||||
|
<string name="profile_error">Impossible de charger le profil</string>
|
||||||
|
<string name="add_images_error">Erreur lors de l\'ajout des images</string>
|
||||||
|
<string name="description_template_summary">Remplir la description des nouveaux messages avec ceci</string>
|
||||||
|
<string name="description_template">Modèle de description</string>
|
||||||
|
<string name="explore_accounts">Explorer les comptes populaires de cette instance</string>
|
||||||
|
<string name="explore_hashtags">Explorer les hashtags en vogue sur cette instance</string>
|
||||||
|
<string name="daily_trending">Voir les messages populaires de la journée</string>
|
||||||
|
<string name="notification_summary_small">%1$s et %2$s</string>
|
||||||
|
<string name="extraneous_pictures_stories">Les images après la première ont été supprimées mais peuvent être restaurées en revenant à la création d\'un message</string>
|
||||||
|
<string name="summary_always_show_nsfw">Les messages NSFW/CW ne seront pas floutés et seront affichés par défaut.</string>
|
||||||
|
<string name="encode_progress">Encodage de %1$d%%</string>
|
||||||
|
<string name="still_encoding">Une ou plusieurs vidéos sont en cours d\'encodage. Attendez qu\'elles soient terminées avant d\'envoyer</string>
|
||||||
|
<string name="notifications_settings_summary">Gérer les notifications que vous souhaitez recevoir</string>
|
||||||
|
<string name="login_notifications">Impossible de récupérer les dernières notifications</string>
|
||||||
|
<string name="no_camera_permission">L\'autorisation pour l\'appareil photo n\'est pas accordée, accordez cette autorisation dans les paramètres si vous voulez permettre à PixelDroid d\'utiliser l\'appareil photo</string>
|
||||||
|
<string name="play_video">Lire la vidéo</string>
|
||||||
|
<string name="public_feed">Publique</string>
|
||||||
|
<string name="accentColorTitle">Couleur d\'accentuation</string>
|
||||||
|
<string name="accentColorSummary">Choisir une couleur d\'accentuation</string>
|
||||||
|
<string name="color_choice_button">Choisir cette couleur d\'accentuation</string>
|
||||||
|
<string name="explore_posts">Explorer aléatoirement les messages d\'aujourd\'hui</string>
|
||||||
|
<string name="grid_view">Vue en grille</string>
|
||||||
|
<string name="feed_view">Vue du flux</string>
|
||||||
|
<string name="encode_error">Erreur d\'encodage</string>
|
||||||
|
<string name="encode_success">Encodage réussi !</string>
|
||||||
|
<string name="more_profile_settings">Autres paramètres du profil</string>
|
||||||
|
<string name="private_account_explanation">Quand votre compte est privé, seules les personnes que vous autorisez peuvent voir vos photos et vidéos sur PixelFed. Les personnes qui vous suivent déjà ne seront pas affectées.</string>
|
||||||
|
<string name="saving_profile">Sauvegarde de votre profil</string>
|
||||||
|
<string name="use_dynamic_color">Utiliser la couleur dynamique de votre système</string>
|
||||||
|
<string name="type_story">Story</string>
|
||||||
|
<string name="story_image">Image de la Story</string>
|
||||||
|
<string name="replyToStory">Répondre à %1$s</string>
|
||||||
|
<string name="story_reply_error">Un problème s\'est produit lors de l\'envoi de la réponse</string>
|
||||||
|
<string name="error_fetch_story">Un problème s\'est produit lors de la récupération du carrousel</string>
|
||||||
|
<string name="sent_reply_story">Envoyer la réponse</string>
|
||||||
|
<string name="fetching_profile">Recherche de votre profil…</string>
|
||||||
|
<string name="redraft_dialog_cancel">Si vous annulez ce remaniement, le message original ne figurera plus sur votre compte. Poursuivre sans réécrire ?</string>
|
||||||
|
<string name="redraft_post_failed_error">Impossible de réécrire le message, erreur %1$d</string>
|
||||||
|
<string name="redraft_post_failed_io_except">Impossible de réécrire le message, vérifiez votre connexion ?</string>
|
||||||
|
<string name="bookmark_post_failed_error">Impossible de mettre/enlever le message en favoris, erreur %1$d</string>
|
||||||
|
<string name="bookmark_post_failed_io_except">Impossible de mettre/enlever le message des favoris, vérifiez votre connexion ?</string>
|
||||||
|
<string name="no_storage_permission">L\'autorisation pour le stockage n\'est pas accordée, accordez l\'autorisation dans les paramètres si vous voulez permettre à PixelDroid d\'afficher la vignette</string>
|
||||||
|
<string name="analyzing_stabilization">Analyse pour stabiliser %1$d%%</string>
|
||||||
|
<string name="color_chosen">Couleur d\'accentuation choisie</string>
|
||||||
|
<string name="error_profile">Un problème s\'est produit. Appuyez pour réessayer</string>
|
||||||
|
<string name="delete_collection_warning">Êtes-vous sûr de vouloir supprimer cette collection ?</string>
|
||||||
|
<string name="added_post_to_collection">Ajouter le message à la collection</string>
|
||||||
|
<string name="error_add_post_to_collection">Échec de l\'ajout du message à la collection</string>
|
||||||
|
<string name="removed_post_from_collection">Enlever le message de la collection</string>
|
||||||
|
<string name="error_remove_post_from_collection">Échec pour retirer le message de la collection</string>
|
||||||
|
<string name="contains_nsfw">Contient des médias NSFW</string>
|
||||||
|
<string name="add_story">Ajouter une Story</string>
|
||||||
|
<string name="story_could_not_see">Erreur : Impossible de marquer la Story comme vu</string>
|
||||||
|
<string name="story_pause">Démarrer ou interrompre les Stories</string>
|
||||||
|
<string name="my_story">Ma Story</string>
|
||||||
|
<string name="type_post">Message</string>
|
||||||
|
<string name="continue_post_creation">Continuer</string>
|
||||||
|
<string name="story_duration">Durée de la Story</string>
|
||||||
</resources>
|
</resources>
|
|
@ -57,7 +57,7 @@
|
||||||
<string name="follow_error">Non se puido seguir</string>
|
<string name="follow_error">Non se puido seguir</string>
|
||||||
<string name="follow_button_failed">Non se pode mostrar o botón para seguir</string>
|
<string name="follow_button_failed">Non se pode mostrar o botón para seguir</string>
|
||||||
<string name="follow_status_failed">Non se obtivo o estado de relacións</string>
|
<string name="follow_status_failed">Non se obtivo o estado de relacións</string>
|
||||||
<string name="comment">Comentar</string>
|
<string name="comment_verb">Comentar</string>
|
||||||
<string name="comment_posted">Comentario: %1$s publicado!</string>
|
<string name="comment_posted">Comentario: %1$s publicado!</string>
|
||||||
<string name="comment_error">Fallo ao comentar!</string>
|
<string name="comment_error">Fallo ao comentar!</string>
|
||||||
<string name="share_image">Compartir Imaxe</string>
|
<string name="share_image">Compartir Imaxe</string>
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
<item quantity="one">%d hozzászólás</item>
|
<item quantity="one">%d hozzászólás</item>
|
||||||
<item quantity="other">%d hozzászólás</item>
|
<item quantity="other">%d hozzászólás</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="comment">Hozzászólás</string>
|
<string name="comment_verb">Hozzászólás</string>
|
||||||
<string name="comment_posted">Hozzászólás: %1$s közzétéve!</string>
|
<string name="comment_posted">Hozzászólás: %1$s közzétéve!</string>
|
||||||
<string name="comment_error">Hozzászólási hiba!</string>
|
<string name="comment_error">Hozzászólási hiba!</string>
|
||||||
<string name="share_image">Kép megosztása</string>
|
<string name="share_image">Kép megosztása</string>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources></resources>
|
|
@ -38,7 +38,7 @@
|
||||||
<string name="empty_comment">Komentar tidak boleh kosong!</string>
|
<string name="empty_comment">Komentar tidak boleh kosong!</string>
|
||||||
<string name="share_image">Bagikan Gambar</string>
|
<string name="share_image">Bagikan Gambar</string>
|
||||||
<string name="comment_posted">Komentar: %1$s diposting!</string>
|
<string name="comment_posted">Komentar: %1$s diposting!</string>
|
||||||
<string name="comment">Komentar</string>
|
<string name="comment_verb">Komentar</string>
|
||||||
<plurals name="number_comments">
|
<plurals name="number_comments">
|
||||||
<item quantity="other">%d komentar</item>
|
<item quantity="other">%d komentar</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<string name="shared_notification">%1$s ha condiviso il tuo post</string>
|
<string name="shared_notification">%1$s ha condiviso il tuo post</string>
|
||||||
<string name="followed_notification">%1$s ti ha seguito</string>
|
<string name="followed_notification">%1$s ti ha seguito</string>
|
||||||
<string name="mention_notification">%1$s ti ha menzionato</string>
|
<string name="mention_notification">%1$s ti ha menzionato</string>
|
||||||
<string name="liked_notification">%1$s è piaciuto il tuo post</string>
|
<string name="liked_notification">A %1$s è piaciuto il tuo post</string>
|
||||||
<string name="post">Invia</string>
|
<string name="post">Invia</string>
|
||||||
<string name="description">Descrizione…</string>
|
<string name="description">Descrizione…</string>
|
||||||
<string name="whats_an_instance">Cos\'è un\'istanza\?</string>
|
<string name="whats_an_instance">Cos\'è un\'istanza\?</string>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
<string name="domain_of_your_instance">Dominio della tua istanza</string>
|
<string name="domain_of_your_instance">Dominio della tua istanza</string>
|
||||||
<string name="login_connection_required_once">Devi essere online per poter aggiungere il primo account e utilizzare PixelDroid :(</string>
|
<string name="login_connection_required_once">Devi essere online per poter aggiungere il primo account e utilizzare PixelDroid :(</string>
|
||||||
<string name="invalid_domain">Dominio non valido</string>
|
<string name="invalid_domain">Dominio non valido</string>
|
||||||
<string name="edit">MODIFICA</string>
|
<string name="edit">Modifica</string>
|
||||||
<string name="instance_error">Impossibile ottenere informazioni sull\'istanza</string>
|
<string name="instance_error">Impossibile ottenere informazioni sull\'istanza</string>
|
||||||
<string name="save_image_failed">Impossibile salvare l\'immagine</string>
|
<string name="save_image_failed">Impossibile salvare l\'immagine</string>
|
||||||
<string name="light_theme">Chiaro</string>
|
<string name="light_theme">Chiaro</string>
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
<string name="share_image">Condividi immagine</string>
|
<string name="share_image">Condividi immagine</string>
|
||||||
<string name="comment_error">Errore nel commento!</string>
|
<string name="comment_error">Errore nel commento!</string>
|
||||||
<string name="comment_posted">Commento: %1$s postato!</string>
|
<string name="comment_posted">Commento: %1$s postato!</string>
|
||||||
<string name="comment">Commento</string>
|
<string name="comment_verb">Commento</string>
|
||||||
<string name="follow_error">Impossibile seguire</string>
|
<string name="follow_error">Impossibile seguire</string>
|
||||||
<string name="unfollow_error">Impossibile smettere di seguire</string>
|
<string name="unfollow_error">Impossibile smettere di seguire</string>
|
||||||
<string name="access_token_invalid">Il token di accesso non è valido</string>
|
<string name="access_token_invalid">Il token di accesso non è valido</string>
|
||||||
|
@ -85,11 +85,11 @@
|
||||||
<string name="instance_not_pixelfed_warning">Questa non sembra essere un\'istanza di Pixelfed, quindi l\'app potrebbe interrompersi in modi inaspettati.</string>
|
<string name="instance_not_pixelfed_warning">Questa non sembra essere un\'istanza di Pixelfed, quindi l\'app potrebbe interrompersi in modi inaspettati.</string>
|
||||||
<string name="instance_not_pixelfed_cancel">Annullare l\'accesso</string>
|
<string name="instance_not_pixelfed_cancel">Annullare l\'accesso</string>
|
||||||
<string name="instance_not_pixelfed_continue">OK, continua comunque</string>
|
<string name="instance_not_pixelfed_continue">OK, continua comunque</string>
|
||||||
<string name="discover">SCOPRI</string>
|
<string name="discover">Scopri</string>
|
||||||
<string name="open_drawer_menu">Apri il menu a scomparsa</string>
|
<string name="open_drawer_menu">Apri il menu a scomparsa</string>
|
||||||
<string name="profile_picture">Foto profilo</string>
|
<string name="profile_picture">Foto profilo</string>
|
||||||
<string name="report_error">Impossibile inviare la segnalazione</string>
|
<string name="report_error">Impossibile inviare la segnalazione</string>
|
||||||
<string name="reported">Segnalato {gmd_check_circle}</string>
|
<string name="reported">Post segnalato</string>
|
||||||
<string name="report_target">Segnala @%1$s\'s post</string>
|
<string name="report_target">Segnala @%1$s\'s post</string>
|
||||||
<string name="optional_report_comment">Messaggio facoltativo per moderatori/amministratori</string>
|
<string name="optional_report_comment">Messaggio facoltativo per moderatori/amministratori</string>
|
||||||
<string name="share_link">Condividi il Link</string>
|
<string name="share_link">Condividi il Link</string>
|
||||||
|
@ -132,25 +132,32 @@
|
||||||
<plurals name="nb_followers">
|
<plurals name="nb_followers">
|
||||||
<item quantity="one">%d
|
<item quantity="one">%d
|
||||||
\nSeguace</item>
|
\nSeguace</item>
|
||||||
|
<item quantity="many">%d
|
||||||
|
\nSeguaci</item>
|
||||||
<item quantity="other">%d
|
<item quantity="other">%d
|
||||||
\nSeguaci</item>
|
\nSeguaci</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="nb_posts">
|
<plurals name="nb_posts">
|
||||||
<item quantity="one">%d
|
<item quantity="one">%d
|
||||||
|
\nPost</item>
|
||||||
|
<item quantity="many">%d
|
||||||
\nPost</item>
|
\nPost</item>
|
||||||
<item quantity="other">%d
|
<item quantity="other">%d
|
||||||
\nPost</item>
|
\nPost</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="number_comments">
|
<plurals name="number_comments">
|
||||||
<item quantity="one">%d commento</item>
|
<item quantity="one">%d commento</item>
|
||||||
|
<item quantity="many">%d commenti</item>
|
||||||
<item quantity="other">%d commenti</item>
|
<item quantity="other">%d commenti</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="shares">
|
<plurals name="shares">
|
||||||
<item quantity="one">%d Condivisione</item>
|
<item quantity="one">%d Condivisione</item>
|
||||||
|
<item quantity="many">%d Condivisioni</item>
|
||||||
<item quantity="other">%d Condivisioni</item>
|
<item quantity="other">%d Condivisioni</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="likes">
|
<plurals name="likes">
|
||||||
<item quantity="one">%d Mi piace</item>
|
<item quantity="one">%d Mi piace</item>
|
||||||
|
<item quantity="many">%d Mi piace</item>
|
||||||
<item quantity="other">%d Mi piace</item>
|
<item quantity="other">%d Mi piace</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="upload_error">Codice di errore restituito dal server: %1$d</string>
|
<string name="upload_error">Codice di errore restituito dal server: %1$d</string>
|
||||||
|
@ -163,11 +170,13 @@
|
||||||
<item quantity="other">La descrizione deve contenere al massimo %d caratteri.</item>
|
<item quantity="other">La descrizione deve contenere al massimo %d caratteri.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="api_not_enabled_dialog">L\'API non è attivata su questa istanza. Contatta l\'amministratore per chiedergli di attivarlo.</string>
|
<string name="api_not_enabled_dialog">L\'API non è attivata su questa istanza. Contatta l\'amministratore per chiedergli di attivarlo.</string>
|
||||||
<string name="delete_post_failed_io_except">Impossibile eliminare il post, controlla la tua connesione\?</string>
|
<string name="delete_post_failed_io_except">Impossibile eliminare il post, controllare la connessione?</string>
|
||||||
<string name="delete_post_failed_error">Impossibile eliminare il post, errore %1$d</string>
|
<string name="delete_post_failed_error">Impossibile eliminare il post, errore %1$d</string>
|
||||||
<plurals name="nb_following">
|
<plurals name="nb_following">
|
||||||
<item quantity="one">%d
|
<item quantity="one">%d
|
||||||
\nUtente che stai seguendo</item>
|
\nUtente che stai seguendo</item>
|
||||||
|
<item quantity="many">%d
|
||||||
|
\nUtenti che stai seguendo</item>
|
||||||
<item quantity="other">%d
|
<item quantity="other">%d
|
||||||
\nUtenti che stai seguendo</item>
|
\nUtenti che stai seguendo</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
@ -206,4 +215,102 @@
|
||||||
<string name="upload_next_step">Prossimo step</string>
|
<string name="upload_next_step">Prossimo step</string>
|
||||||
<string name="add_details">Aggiungi qualche dettaglio</string>
|
<string name="add_details">Aggiungi qualche dettaglio</string>
|
||||||
<string name="unknown_error_in_error">Errore sconosciuto, controlla se il server è giù: %1$s</string>
|
<string name="unknown_error_in_error">Errore sconosciuto, controlla se il server è giù: %1$s</string>
|
||||||
|
<string name="feed_view">Vista a elenco</string>
|
||||||
|
<string name="follow_requested">Follow Richiesto</string>
|
||||||
|
<string name="bookmark">Aggiungi ai preferiti</string>
|
||||||
|
<string name="unbookmark">Rimuovi dai preferiti</string>
|
||||||
|
<string name="redraft">Rielabora</string>
|
||||||
|
<string name="from_other_domain">da %1$s</string>
|
||||||
|
<string name="redraft_dialog_launch">Rielaborare questo post ti permetterà di modificare la foto e la sua descrizione, ma eliminerà tutti i commenti e i mi piace attualmente presenti. Continuare?</string>
|
||||||
|
<string name="bookmark_post_failed_io_except">Non è stato possibile aggiungere/rimuovere il post dai preferiti, controllare la connessione?</string>
|
||||||
|
<string name="no_storage_permission">Accesso all\'archiviazione non autorizzato, aggiungi l\'autorizzazione nelle impostazioni se vuoi permettere a PixelDroid di mostrare le miniature</string>
|
||||||
|
<string name="description_template_summary">Inserisci questo nella descrizione dei post</string>
|
||||||
|
<string name="dialog_message_cancel_follow_request">Cancellare la richiesta di follow?</string>
|
||||||
|
<string name="home_feed">Home</string>
|
||||||
|
<string name="search_discover_feed">Cerca</string>
|
||||||
|
<string name="create_feed">Aggiungi</string>
|
||||||
|
<string name="notifications_feed">Aggiornamenti</string>
|
||||||
|
<string name="public_feed">Pubblico</string>
|
||||||
|
<string name="accentColorTitle">Colore secondario</string>
|
||||||
|
<string name="accentColorSummary">Scegli un colore secondario</string>
|
||||||
|
<string name="color_choice_button">Scegli questo colore secondario</string>
|
||||||
|
<string name="color_chosen">Colore secondario scelto</string>
|
||||||
|
<string name="redraft_dialog_cancel">Se elimini questa rielaborazione, il post originale non sarà più sul tuo profilo. Continuare senza ripubblicare?</string>
|
||||||
|
<string name="redraft_post_failed_error">Impossibile rielaborare il post, errore %1$d</string>
|
||||||
|
<string name="redraft_post_failed_io_except">Non è stato possibile rielaborare il post, controllare la connessione?</string>
|
||||||
|
<string name="bookmark_post_failed_error">Non è stato possibile aggiungere/rimuovere il post dai preferiti, errore %1$d</string>
|
||||||
|
<string name="no_camera_permission">Accesso alla fotocamera non autorizzato, aggiungi l\'autorizzazione nelle impostazioni se vuoi permettere a PixelDroid di usare la fotocamera</string>
|
||||||
|
<string name="play_video">Riproduci video</string>
|
||||||
|
<string name="encode_error">Errore durante la codifica</string>
|
||||||
|
<string name="encode_success">Codifica completata!</string>
|
||||||
|
<string name="encode_progress">Codifica %1$d%%</string>
|
||||||
|
<string name="analyzing_stabilization">Analisi per la stabilizzazione %1$d%%</string>
|
||||||
|
<string name="still_encoding">Uno o più video sono ancora in fase di codifica. Aspetta che siano pronti prima di caricare</string>
|
||||||
|
<string name="new_post_shortcut_short">Nuovo post</string>
|
||||||
|
<string name="follow_request">%1$s vorrebbe seguirti</string>
|
||||||
|
<string name="status_notification">%1$s ha aggiunto un post</string>
|
||||||
|
<string name="new_post_shortcut_long">Aggiungi un nuovo post</string>
|
||||||
|
<string name="profile_error">Impossibile caricare il profilo</string>
|
||||||
|
<string name="add_images_error">Errore durante l\'aggiunto delle immagini</string>
|
||||||
|
<string name="notification_thumbnail">Miniatura dell\'immagine nella notifica di questo post</string>
|
||||||
|
<string name="post_preview">Anteprima di un post</string>
|
||||||
|
<string name="description_template">Modello di descrizione</string>
|
||||||
|
<string name="explore_accounts">Scopri profili popolari in questa istanza</string>
|
||||||
|
<string name="popular_accounts">Profili popolari</string>
|
||||||
|
<string name="explore_hashtags">Scopri gli hashtag di tendenza in questa istanza</string>
|
||||||
|
<string name="trending_hashtags">Hashtag di tendenza</string>
|
||||||
|
<string name="daily_trending">Vedi i post di tendenza oggi</string>
|
||||||
|
<string name="trending_posts">Post di tendenza</string>
|
||||||
|
<string name="explore_posts">Scopri post casuali di oggi</string>
|
||||||
|
<string name="grid_view">Vista a griglia</string>
|
||||||
|
<string name="bookmarks">Preferiti</string>
|
||||||
|
<string name="comment_noun">Commento</string>
|
||||||
|
<string name="delete_collection_warning">Vuoi davvero eliminare questa raccolta?</string>
|
||||||
|
<string name="removed_post_from_collection">Post rimosso dalla raccolta</string>
|
||||||
|
<string name="new_collection_link_failed">Impossibile aprire la pagina per aggiungere una raccolta</string>
|
||||||
|
<string name="private_account_explanation">Se il tuo account è privato, solo le persone che approvi possono vedere le tue foto e i tuoi video su Pixelfed. I tuoi seguaci esistenti non subiranno modifiche.</string>
|
||||||
|
<plurals name="replies_count">
|
||||||
|
<item quantity="one">%d risposta</item>
|
||||||
|
<item quantity="many">%d risposte</item>
|
||||||
|
<item quantity="other">%d risposte</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="collection_title">La raccolta di %1$s</string>
|
||||||
|
<string name="story_image">Immagine della storia</string>
|
||||||
|
<string name="replyToStory">Rispondi a %1$s</string>
|
||||||
|
<string name="story_reply_error">Qualcosa è andato storto nell\'invio della risposta</string>
|
||||||
|
<string name="error_fetch_story">Qualcosa è andato storto nel recupero del carosello</string>
|
||||||
|
<string name="sent_reply_story">Risposta inviata</string>
|
||||||
|
<string name="fetching_profile">Recupero del tuo profilo…</string>
|
||||||
|
<string name="collection_remove_post">Rimuovi post</string>
|
||||||
|
<string name="add_to_collection">Scegli un post da aggiungere</string>
|
||||||
|
<string name="delete_from_collection">Scegli un post da rimuovere</string>
|
||||||
|
<string name="added_post_to_collection">Post aggiunto alla raccolta</string>
|
||||||
|
<string name="error_add_post_to_collection">Impossibile aggiungere il post alla raccolta</string>
|
||||||
|
<string name="error_remove_post_from_collection">Impossibile rimuovere il post dalla raccolta</string>
|
||||||
|
<string name="save">Salva</string>
|
||||||
|
<string name="use_dynamic_color">Usa il colore dinamico dal tuo sistema</string>
|
||||||
|
<string name="more_profile_settings">Altre impostazioni del profilo</string>
|
||||||
|
<string name="private_account">Account privato</string>
|
||||||
|
<string name="your_bio">La tua bio</string>
|
||||||
|
<string name="your_name">Il tuo nome</string>
|
||||||
|
<string name="profile_save_changes">Non hai salvato le modifiche. Uscire?</string>
|
||||||
|
<string name="saving_profile">Salvataggio del tuo profilo</string>
|
||||||
|
<string name="profile_saved">Modifiche salvate!</string>
|
||||||
|
<string name="error_profile">Qualcosa è andato storto. Tocca per riprovare</string>
|
||||||
|
<string name="change_profile_picture">Cambia la tua immagine del profilo</string>
|
||||||
|
<string name="contains_nsfw">Contiene media sensibili</string>
|
||||||
|
<string name="switch_accounts">Cambia account</string>
|
||||||
|
<string name="summary_always_show_nsfw">I post con contenuti sensibili non verranno oscurati e saranno mostrati di default.</string>
|
||||||
|
<string name="collection_add_post">Aggiungi post</string>
|
||||||
|
<string name="collections">Raccolte</string>
|
||||||
|
<string name="delete_collection">Elimina raccolta</string>
|
||||||
|
<string name="add_story">Aggiungi Storia</string>
|
||||||
|
<string name="story_pause">Riproduci o ferma le storie</string>
|
||||||
|
<string name="story_could_not_see">Errore: impossibile segnare la storia come visualizzata</string>
|
||||||
|
<string name="my_story">La mia storia</string>
|
||||||
|
<string name="type_story">Storia</string>
|
||||||
|
<string name="type_post">Post</string>
|
||||||
|
<string name="continue_post_creation">Continua</string>
|
||||||
|
<string name="extraneous_pictures_stories">Le immagini successive alla prima sono state rimosse, ma possono essere ripristinate tornando indietro alla creazione di un Post</string>
|
||||||
|
<string name="story_duration">Durata della Storia</string>
|
||||||
</resources>
|
</resources>
|
|
@ -64,7 +64,7 @@
|
||||||
<string name="follow_error">フォローできませんでした</string>
|
<string name="follow_error">フォローできませんでした</string>
|
||||||
<string name="follow_button_failed">フォローボタンを表示できませんでした</string>
|
<string name="follow_button_failed">フォローボタンを表示できませんでした</string>
|
||||||
<string name="follow_status_failed">フォローステータスを取得できませんでした</string>
|
<string name="follow_status_failed">フォローステータスを取得できませんでした</string>
|
||||||
<string name="comment">コメント</string>
|
<string name="comment_verb">コメント</string>
|
||||||
<string name="comment_error">コメントエラー</string>
|
<string name="comment_error">コメントエラー</string>
|
||||||
<string name="share_image">画像を共有</string>
|
<string name="share_image">画像を共有</string>
|
||||||
<string name="no_description">説明はありません</string>
|
<string name="no_description">説明はありません</string>
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
<string name="instance_error">Kon instance-informatie niet ophalen</string>
|
<string name="instance_error">Kon instance-informatie niet ophalen</string>
|
||||||
<string name="request_format_error">Fout bij het uploaden: slecht verzoekformaat</string>
|
<string name="request_format_error">Fout bij het uploaden: slecht verzoekformaat</string>
|
||||||
<string name="posted_on">Gepost op %1$s</string>
|
<string name="posted_on">Gepost op %1$s</string>
|
||||||
<string name="comment">Commentaar maken</string>
|
<string name="comment_verb">Commentaar maken</string>
|
||||||
<string name="comment_posted">Commentaar: %1$s gepost!</string>
|
<string name="comment_posted">Commentaar: %1$s gepost!</string>
|
||||||
<string name="comment_error">Fout met commentaar!</string>
|
<string name="comment_error">Fout met commentaar!</string>
|
||||||
<string name="share_image">Afbeelding delen</string>
|
<string name="share_image">Afbeelding delen</string>
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<string name="share_image">Udostępnij</string>
|
<string name="share_image">Udostępnij</string>
|
||||||
<string name="comment_error">Błąd komentarza!</string>
|
<string name="comment_error">Błąd komentarza!</string>
|
||||||
<string name="comment_posted">Komentarz: %1$s opublikowany!</string>
|
<string name="comment_posted">Komentarz: %1$s opublikowany!</string>
|
||||||
<string name="comment">Komentarz</string>
|
<string name="comment_verb">Komentarz</string>
|
||||||
<string name="menu_account">Mój profil</string>
|
<string name="menu_account">Mój profil</string>
|
||||||
<string name="registration_failed">Nie udało się zarejestrować aplikacji na tym serwerze</string>
|
<string name="registration_failed">Nie udało się zarejestrować aplikacji na tym serwerze</string>
|
||||||
<string name="instance_error">Nie udało się pobrać informacji o instancji</string>
|
<string name="instance_error">Nie udało się pobrać informacji o instancji</string>
|
||||||
|
|
|
@ -158,7 +158,7 @@
|
||||||
<string name="unfollow_error">Não foi possível deixar de seguir</string>
|
<string name="unfollow_error">Não foi possível deixar de seguir</string>
|
||||||
<string name="action_not_allowed">Esta ação não é permitida</string>
|
<string name="action_not_allowed">Esta ação não é permitida</string>
|
||||||
<string name="follow_error">Não foi possível seguir</string>
|
<string name="follow_error">Não foi possível seguir</string>
|
||||||
<string name="comment">Comentar</string>
|
<string name="comment_verb">Comentar</string>
|
||||||
<plurals name="description_max_characters">
|
<plurals name="description_max_characters">
|
||||||
<item quantity="one">A descrição precisa ter pelo menos %d carácter.</item>
|
<item quantity="one">A descrição precisa ter pelo menos %d carácter.</item>
|
||||||
<item quantity="other">A descrição precisa ter pelo menos %d caracteres.</item>
|
<item quantity="other">A descrição precisa ter pelo menos %d caracteres.</item>
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
<string name="comment_error">Erro ao comentar!</string>
|
<string name="comment_error">Erro ao comentar!</string>
|
||||||
<string name="write_permission_download_pic">Precisa adicionar a permissão para transferir imagens!</string>
|
<string name="write_permission_download_pic">Precisa adicionar a permissão para transferir imagens!</string>
|
||||||
<string name="comment_posted">Comentário: %1$s publicou!</string>
|
<string name="comment_posted">Comentário: %1$s publicou!</string>
|
||||||
<string name="comment">Comentário</string>
|
<string name="comment_verb">Comentário</string>
|
||||||
<string name="add_comment">Adicionar um comentário</string>
|
<string name="add_comment">Adicionar um comentário</string>
|
||||||
<string name="submit_comment">Submeter comentário</string>
|
<string name="submit_comment">Submeter comentário</string>
|
||||||
<string name="post_is_album">Esta publicação é um álbum</string>
|
<string name="post_is_album">Esta publicação é um álbum</string>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<string name="add_account_description">Добавить другой аккаунт Pixelfed</string>
|
<string name="add_account_description">Добавить другой аккаунт Pixelfed</string>
|
||||||
<string name="add_account_name">Добавить аккаунт</string>
|
<string name="add_account_name">Добавить аккаунт</string>
|
||||||
<string name="instance_error">Не удалось получить информацию об экземпляре</string>
|
<string name="instance_error">Не удалось получить информацию об экземпляре</string>
|
||||||
<string name="comment">Комментировать</string>
|
<string name="comment_verb">Комментировать</string>
|
||||||
<string name="comment_posted">Комментарий: %1$s опубликован!</string>
|
<string name="comment_posted">Комментарий: %1$s опубликован!</string>
|
||||||
<string name="comment_error">Ошибка комментирования!</string>
|
<string name="comment_error">Ошибка комментирования!</string>
|
||||||
<string name="share_image">Поделиться изображением</string>
|
<string name="share_image">Поделиться изображением</string>
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
<string name="follow_error">Kunde inte följa</string>
|
<string name="follow_error">Kunde inte följa</string>
|
||||||
<string name="follow_button_failed">Kunde inte visa följarknapp</string>
|
<string name="follow_button_failed">Kunde inte visa följarknapp</string>
|
||||||
<string name="follow_status_failed">Kunde inte hämta status för följare</string>
|
<string name="follow_status_failed">Kunde inte hämta status för följare</string>
|
||||||
<string name="comment">Kommentera</string>
|
<string name="comment_verb">Kommentera</string>
|
||||||
<string name="comment_posted">Kommentar: %1$s inlagd!</string>
|
<string name="comment_posted">Kommentar: %1$s inlagd!</string>
|
||||||
<string name="comment_error">Kommentarsfel!</string>
|
<string name="comment_error">Kommentarsfel!</string>
|
||||||
<string name="share_image">Dela bild</string>
|
<string name="share_image">Dela bild</string>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<string name="theme_header">Тема</string>
|
<string name="theme_header">Тема</string>
|
||||||
<string name="mention_notification">%1$s вас згадує</string>
|
<string name="mention_notification">%1$s вас згадує</string>
|
||||||
<string name="description">Опис…</string>
|
<string name="description">Опис…</string>
|
||||||
<string name="post">надіслати</string>
|
<string name="post">Оприлюднити</string>
|
||||||
<string name="save_to_gallery">Зберегти до галереї…</string>
|
<string name="save_to_gallery">Зберегти до галереї…</string>
|
||||||
<string name="image_download_downloading">Завантаження…</string>
|
<string name="image_download_downloading">Завантаження…</string>
|
||||||
<string name="image_download_success">Зображення успішно завантажено</string>
|
<string name="image_download_success">Зображення успішно завантажено</string>
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
<string name="instance_not_pixelfed_cancel">Скасувати вхід</string>
|
<string name="instance_not_pixelfed_cancel">Скасувати вхід</string>
|
||||||
<string name="request_format_error">Помилка вивантаження: хибний формат запиту</string>
|
<string name="request_format_error">Помилка вивантаження: хибний формат запиту</string>
|
||||||
<string name="connect_to_pixelfed">З\'єднатися з Pixelfed</string>
|
<string name="connect_to_pixelfed">З\'єднатися з Pixelfed</string>
|
||||||
<string name="comment">Коментар</string>
|
<string name="comment_verb">Коментар</string>
|
||||||
<plurals name="nb_posts">
|
<plurals name="nb_posts">
|
||||||
<item quantity="one">%d
|
<item quantity="one">%d
|
||||||
\nдопис</item>
|
\nдопис</item>
|
||||||
|
@ -146,13 +146,13 @@
|
||||||
<string name="default_nfollowing">-
|
<string name="default_nfollowing">-
|
||||||
\nпідписок</string>
|
\nпідписок</string>
|
||||||
<string name="search">Пошук</string>
|
<string name="search">Пошук</string>
|
||||||
<string name="edit_profile">Редагувати</string>
|
<string name="edit_profile">Редагувати профіль</string>
|
||||||
<string name="posts">ДОПИСИ</string>
|
<string name="posts">ДОПИСИ</string>
|
||||||
<string name="accounts">ОБЛІКОВІ ЗАПИСИ</string>
|
<string name="accounts">ОБЛІКОВІ ЗАПИСИ</string>
|
||||||
<string name="license_info">PixelDroid — вільне й відкрите програмне забезпечення, доступне на умовах Загальної громадської ліцензії GNU (версії 3 чи новішої)</string>
|
<string name="license_info">PixelDroid — вільне й відкрите програмне забезпечення, доступне на умовах Загальної громадської ліцензії GNU (версії 3 чи новішої)</string>
|
||||||
<string name="about">Про застосунок</string>
|
<string name="about">Про застосунок</string>
|
||||||
<string name="post_title">Допис %1$s</string>
|
<string name="post_title">Допис %1$s</string>
|
||||||
<string name="reported">Скаргу надіслано {gmd_check_circle}</string>
|
<string name="reported">Скаргу надіслано</string>
|
||||||
<string name="profile_picture">Зображення профілю</string>
|
<string name="profile_picture">Зображення профілю</string>
|
||||||
<string name="open_drawer_menu">Відкрити висувне меню</string>
|
<string name="open_drawer_menu">Відкрити висувне меню</string>
|
||||||
<string name="something_went_wrong">Щось пішло не так…</string>
|
<string name="something_went_wrong">Щось пішло не так…</string>
|
||||||
|
@ -223,4 +223,108 @@
|
||||||
<string name="video_not_supported">Використовуваний сервер не підтримує вивантаження відео. Ймовірно, вивантажити відео цього допису не вдасться</string>
|
<string name="video_not_supported">Використовуваний сервер не підтримує вивантаження відео. Ймовірно, вивантажити відео цього допису не вдасться</string>
|
||||||
<string name="post_is_video">Це відеодопис</string>
|
<string name="post_is_video">Це відеодопис</string>
|
||||||
<string name="play_video">Відтворити відео</string>
|
<string name="play_video">Відтворити відео</string>
|
||||||
|
<string name="bookmarks">Закладки</string>
|
||||||
|
<string name="collections">Колекції</string>
|
||||||
|
<string name="delete_collection">Видалити колекцію</string>
|
||||||
|
<string name="collection_add_post">Додати публікацію</string>
|
||||||
|
<string name="collection_remove_post">Вилучити публікацію</string>
|
||||||
|
<string name="comment_noun">Коментар</string>
|
||||||
|
<string name="add_to_collection">Оберіть публікацію для додавання</string>
|
||||||
|
<plurals name="replies_count">
|
||||||
|
<item quantity="one">%d відповідь</item>
|
||||||
|
<item quantity="few">%d відповіді</item>
|
||||||
|
<item quantity="many">%d відповідей</item>
|
||||||
|
<item quantity="other">%d відповідей</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="delete_from_collection">Оберіть публікацію для вилучення</string>
|
||||||
|
<string name="redraft_dialog_cancel">Якщо ви скасуєте цю переробку, оригінального допису більше не буде у вашому акаунті. Продовжити без перепосту?</string>
|
||||||
|
<string name="accentColorTitle">Акцентний колір</string>
|
||||||
|
<string name="explore_posts">Ознайомтеся з випадковими дописами дня</string>
|
||||||
|
<string name="private_account_explanation">Коли ваш акаунт приватний, тільки люди, яких ви схвалите, зможуть бачити ваші фотографії та відео на pixelfed. На ваших існуючих підписників це не вплине.</string>
|
||||||
|
<string name="always_show_nsfw">Завжди показувати чутливий вміст</string>
|
||||||
|
<string name="profile_error">Не вдалося завантажити профіль</string>
|
||||||
|
<string name="explore_accounts">Ознайомтеся з популярними акаунтами на цьому екземплярі</string>
|
||||||
|
<string name="added_post_to_collection">Публікацію додано в колекцію</string>
|
||||||
|
<string name="summary_always_show_nsfw">Дописи NSFW/CW не будуть розмиті і відображатимуться за замовчуванням.</string>
|
||||||
|
<string name="type_story">Історія</string>
|
||||||
|
<string name="new_collection_link_failed">Не вдалося відкрити сторінку створення колекції</string>
|
||||||
|
<string name="redraft_dialog_launch">При переробці цього допису ви зможете відредагувати фотографію та її опис, але при цьому будуть видалені всі поточні коментарі та вподобання. Продовжити?</string>
|
||||||
|
<string name="notification_thumbnail">Ескіз зображення в дописі цього повідомлення</string>
|
||||||
|
<string name="redraft">Переробити</string>
|
||||||
|
<string name="delete_collection_warning">Ви впевнені що хочете видалити цю колекцію?</string>
|
||||||
|
<string name="error_add_post_to_collection">Не вдалося додати публікацію до колекції</string>
|
||||||
|
<string name="use_dynamic_color">Використовувати динамічні кольори з вашої системи</string>
|
||||||
|
<string name="story_could_not_see">Помилка: не вдалося позначити історію як побачену</string>
|
||||||
|
<string name="encode_error">Помилка кодування</string>
|
||||||
|
<string name="encode_success">Кодування успішно завершено!</string>
|
||||||
|
<string name="encode_progress">Кодувати %1$d%%</string>
|
||||||
|
<string name="still_encoding">Одне або кілька відео все ще кодуються. Зачекайте, поки кодування завершиться, перш ніж завантажувати</string>
|
||||||
|
<string name="new_post_shortcut_short">Нова публікація</string>
|
||||||
|
<string name="follow_request">%1$s надіслав запит на відстеження вас</string>
|
||||||
|
<plurals name="items_load_success">
|
||||||
|
<item quantity="one">%d елемент успішно завантажено</item>
|
||||||
|
<item quantity="few">%d елемента успішно завантажено</item>
|
||||||
|
<item quantity="many">%d елементів успішно завантажено</item>
|
||||||
|
<item quantity="other">%d елементів успішно завантажено</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="home_feed">Домівка</string>
|
||||||
|
<string name="search_discover_feed">Пошук</string>
|
||||||
|
<string name="story_image">Зображення історії</string>
|
||||||
|
<string name="replyToStory">Відповісти на %1$s</string>
|
||||||
|
<string name="fetching_profile">Отримання вашого профілю…</string>
|
||||||
|
<string name="continue_post_creation">Продовжити</string>
|
||||||
|
<string name="extraneous_pictures_stories">Зображення після першого були вилучені, але їх можна відновити, повернувшись до створення публікації</string>
|
||||||
|
<string name="upload_next_step">Наступний крок</string>
|
||||||
|
<string name="add_details">Додати деякі деталі</string>
|
||||||
|
<string name="unknown_error_in_error">Невідома помилка, перевірте, чи не працює сервер: %1$s</string>
|
||||||
|
<string name="collection_title">%1$s колекція</string>
|
||||||
|
<string name="bookmark">Закладка</string>
|
||||||
|
<string name="unbookmark">Видалити закладку</string>
|
||||||
|
<string name="feed_view">Вигляд стрічки</string>
|
||||||
|
<string name="redraft_post_failed_error">Не вдалося переробити пост, помилка %1$d</string>
|
||||||
|
<string name="redraft_post_failed_io_except">Не вдалося переписати пост, перевірити ваше зʼєднання?</string>
|
||||||
|
<string name="bookmark_post_failed_error">Не вдалося додати/видалити закладку, помилка %1$d</string>
|
||||||
|
<string name="bookmark_post_failed_io_except">Не вдалося додати/видалити закладку, перевірити зʼєднання?</string>
|
||||||
|
<string name="analyzing_stabilization">Аналіз для стабілізації %1$d%%</string>
|
||||||
|
<string name="new_post_shortcut_long">Створити нову публікацію</string>
|
||||||
|
<string name="status_notification">%1$s створив публікацію</string>
|
||||||
|
<string name="create_feed">Створити</string>
|
||||||
|
<string name="notifications_feed">Оновлення</string>
|
||||||
|
<string name="public_feed">Публічний</string>
|
||||||
|
<string name="accentColorSummary">Виберіть акцентний колір</string>
|
||||||
|
<string name="color_choice_button">Вибрати цей колір як акцентний</string>
|
||||||
|
<string name="color_chosen">Обрано акцентний колір</string>
|
||||||
|
<string name="from_other_domain">з %1$s</string>
|
||||||
|
<string name="add_images_error">Помилка під час додавання зображень</string>
|
||||||
|
<string name="post_preview">Попередній перегляд публікації</string>
|
||||||
|
<string name="description_template_summary">Доповніть опис нових дописів цим</string>
|
||||||
|
<string name="description_template">Шаблон опису</string>
|
||||||
|
<string name="popular_accounts">Популярні акаунти</string>
|
||||||
|
<string name="explore_hashtags">Дослідіть популярні хештеги на цьому екземплярі</string>
|
||||||
|
<string name="trending_hashtags">Популярні хештеги</string>
|
||||||
|
<string name="daily_trending">Переглядайте щоденні популярні дописи</string>
|
||||||
|
<string name="trending_posts">Популярні публікації</string>
|
||||||
|
<string name="grid_view">Вигляд сітки</string>
|
||||||
|
<string name="error_remove_post_from_collection">Не вдалося вилучити публікацію з колекції</string>
|
||||||
|
<string name="removed_post_from_collection">Публікацію вилучено з колекції</string>
|
||||||
|
<string name="save">Зберегти</string>
|
||||||
|
<string name="more_profile_settings">Більше налаштувань профілю</string>
|
||||||
|
<string name="private_account">Приватний обліковий запис</string>
|
||||||
|
<string name="your_bio">Ваша біографія</string>
|
||||||
|
<string name="your_name">Ваге імʼя</string>
|
||||||
|
<string name="profile_save_changes">Ви не зберегли зміни. Вийти?</string>
|
||||||
|
<string name="saving_profile">Збереження вашого профілю</string>
|
||||||
|
<string name="profile_saved">Зміни збережено!</string>
|
||||||
|
<string name="error_profile">Щось пішло не так. Натисніть, щоб повторити спробу</string>
|
||||||
|
<string name="change_profile_picture">Змінити зображення профілю</string>
|
||||||
|
<string name="contains_nsfw">Містить носії NSFW</string>
|
||||||
|
<string name="switch_accounts">Змінити обліковий запис</string>
|
||||||
|
<string name="story_reply_error">Щось пішло не так під час надсилання відповіді</string>
|
||||||
|
<string name="error_fetch_story">Щось пішло не так з каруселлю</string>
|
||||||
|
<string name="sent_reply_story">Відповідь надіслано</string>
|
||||||
|
<string name="add_story">Додати історію</string>
|
||||||
|
<string name="story_pause">Запустити або призупинити історії</string>
|
||||||
|
<string name="my_story">Моя історія</string>
|
||||||
|
<string name="type_post">Публікація</string>
|
||||||
|
<string name="story_duration">Тривалість історії</string>
|
||||||
</resources>
|
</resources>
|
|
@ -60,7 +60,7 @@
|
||||||
<string name="follow_error">无法关注</string>
|
<string name="follow_error">无法关注</string>
|
||||||
<string name="follow_button_failed">无法显示关注按钮</string>
|
<string name="follow_button_failed">无法显示关注按钮</string>
|
||||||
<string name="follow_status_failed">无法获得关注状态</string>
|
<string name="follow_status_failed">无法获得关注状态</string>
|
||||||
<string name="comment">评论</string>
|
<string name="comment_verb">评论</string>
|
||||||
<string name="comment_posted">评论: %1$s 已发布!</string>
|
<string name="comment_posted">评论: %1$s 已发布!</string>
|
||||||
<string name="comment_error">评论错误!</string>
|
<string name="comment_error">评论错误!</string>
|
||||||
<string name="share_image">分享图像</string>
|
<string name="share_image">分享图像</string>
|
||||||
|
|
|
@ -0,0 +1,291 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="menu_settings">設定</string>
|
||||||
|
<string name="menu_account">關於我</string>
|
||||||
|
<string name="invalid_domain">無效的網域</string>
|
||||||
|
<string name="registration_failed">無法在此伺服器上註冊</string>
|
||||||
|
<string name="auth_failed">無法認證</string>
|
||||||
|
<string name="verify_credentials">無法獲取使用者資訊</string>
|
||||||
|
<string name="token_error">獲取Token的時候發生錯誤</string>
|
||||||
|
<string name="instance_error">無法獲取實例資訊</string>
|
||||||
|
<string name="instance_not_pixelfed_continue">無論如何繼續</string>
|
||||||
|
<string name="instance_not_pixelfed_cancel">取消登入</string>
|
||||||
|
<string name="title_activity_settings2">設定</string>
|
||||||
|
<string name="theme_title">應用主題</string>
|
||||||
|
<string name="theme_header">主題</string>
|
||||||
|
<string name="default_system">默認(跟隨系統)</string>
|
||||||
|
<string name="light_theme">明亮</string>
|
||||||
|
<string name="dark_theme">黑暗</string>
|
||||||
|
<string name="always_show_nsfw">總是展示敏感內容</string>
|
||||||
|
<string name="followed_notification">%1$s 追蹤了你</string>
|
||||||
|
<string name="mention_notification">%1$s 提及了你</string>
|
||||||
|
<string name="shared_notification">%1$s 分享了你的貼文</string>
|
||||||
|
<string name="comment_notification">%1$s 在你的貼文下留言了</string>
|
||||||
|
<string name="poll_notification">%1$s的投票已經結束</string>
|
||||||
|
<string name="other_notification">從%1$s的通知</string>
|
||||||
|
<string name="followed_notification_channel">新的追蹤者</string>
|
||||||
|
<string name="mention_notification_channel">提及</string>
|
||||||
|
<string name="shared_notification_channel">分享</string>
|
||||||
|
<string name="liked_notification_channel">喜歡</string>
|
||||||
|
<string name="comment_notification_channel">留言</string>
|
||||||
|
<string name="poll_notification_channel">投票</string>
|
||||||
|
<string name="other_notification_channel">其他</string>
|
||||||
|
<string name="notification_summary_large">%1$s, %2$s, %3$s 和 %4$d 更多</string>
|
||||||
|
<string name="notification_summary_medium">%1$s, %2$s, 和 %3$s</string>
|
||||||
|
<string name="notification_summary_small">%1$s 和 %2$s</string>
|
||||||
|
<string name="whats_an_instance">「實例」是什麼?</string>
|
||||||
|
<string name="domain_of_your_instance">你所在實例的網域</string>
|
||||||
|
<string name="connect_to_pixelfed">連接至Pixelfed</string>
|
||||||
|
<string name="login_connection_required_once">你需要連上網際網路才能新增第一個賬戶來使用PixelDroid :(</string>
|
||||||
|
<string name="api_not_enabled_dialog">這個實例上的API並沒有被啟用。請與你的管理員聯絡。</string>
|
||||||
|
<string name="logout">登出</string>
|
||||||
|
<plurals name="description_max_characters">
|
||||||
|
<item quantity="other">介紹必須含有最少%d個字元。</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="upload_picture_failed">上載圖片失敗!</string>
|
||||||
|
<string name="picture_format_error">上載失敗:錯誤的圖片格式。</string>
|
||||||
|
<string name="request_format_error">上載失敗:請求格式錯誤</string>
|
||||||
|
<string name="upload_post_failed">貼文上載失敗</string>
|
||||||
|
<string name="upload_post_error">貼文上載錯誤</string>
|
||||||
|
<string name="description">介紹……</string>
|
||||||
|
<string name="post">貼文</string>
|
||||||
|
<string name="upload_next_step">下一步</string>
|
||||||
|
<string name="add_details">新增點細節</string>
|
||||||
|
<string name="add_photo">新增圖片</string>
|
||||||
|
<string name="post_image">本貼文中的一個圖片</string>
|
||||||
|
<string name="switch_to_grid">切換到網格視圖</string>
|
||||||
|
<string name="save_image_description">保存圖片介紹</string>
|
||||||
|
<string name="no_media_description">在這裡新增媒體描述……</string>
|
||||||
|
<string name="size_exceeds_instance_limit">第%1$d個圖片超過了實例所允許的最大大小(%2$dkB,限制為%3$dkB),你可能無法上載。</string>
|
||||||
|
<string name="upload_error">伺服器返回了「%1$d」的錯誤碼</string>
|
||||||
|
<string name="capture_button_alt">拍照</string>
|
||||||
|
<string name="switch_camera_button_alt">切換攝像頭</string>
|
||||||
|
<string name="gallery_button_alt">相簿</string>
|
||||||
|
<string name="loading_toast">載入時發生錯誤</string>
|
||||||
|
<string name="feed_failed">無法獲得Feed</string>
|
||||||
|
<string name="unknown_error_in_error">未知錯誤,看看伺服器是否停運:%1$s</string>
|
||||||
|
<string name="browser_launch_failed">無法拉起瀏覽器,你有安裝嗎?</string>
|
||||||
|
<string name="app_name">PixelDroid</string>
|
||||||
|
<string name="instance_not_pixelfed_warning">這看起來不是個Pixelfed實例,應用可能會以意想不到的方式崩潰。</string>
|
||||||
|
<string name="liked_notification">%1$s 喜歡了你的貼文</string>
|
||||||
|
<string name="upload_post_success">成功上載了貼文</string>
|
||||||
|
<string name="switch_to_carousel">切換到輪播</string>
|
||||||
|
<string name="video_not_supported">你選擇的伺服器不支援上載影片,你可能無法在此貼文中新增影片</string>
|
||||||
|
<plurals name="notification_title_summary">
|
||||||
|
<item quantity="other">%d 則新通知</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="whats_an_instance_explanation">你可能因被要求填入「實例」而困惑。
|
||||||
|
\n
|
||||||
|
\nPixelfed是個聯邦平台,「聯邦宇宙」的一員,可以和其他平台上相同語言的使用者相互交談,就像Mastodon(去https://joinmastodon.org看看)
|
||||||
|
\n
|
||||||
|
\n也就是說你必須選擇一個伺服器,或者是Pixelfed的「實例」來使用。如果沒有的話請訪問https://pixelfed.org/join
|
||||||
|
\n
|
||||||
|
\n欲瞭解Pixelfed的更多資訊,請訪問https://pixelfed.org</string>
|
||||||
|
<string name="add_account_name">新增賬戶</string>
|
||||||
|
<string name="add_account_description">新增另一個Pixelfed賬戶</string>
|
||||||
|
<string name="total_exceeds_album_limit">你可以選擇該伺服器最多允許的圖片數量(%1$s個)。超過限制的會被忽略。</string>
|
||||||
|
<string name="share_picture">分享相片……</string>
|
||||||
|
<string name="save_to_gallery">存儲至相簿……</string>
|
||||||
|
<string name="image_download_failed">下載失敗,請重試</string>
|
||||||
|
<string name="image_download_downloading">下載中……</string>
|
||||||
|
<string name="image_download_success">相片下載成功</string>
|
||||||
|
<plurals name="items_load_success">
|
||||||
|
<item quantity="other">%d個物件已被成功載入</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="no_description">沒有描述</string>
|
||||||
|
<plurals name="likes">
|
||||||
|
<item quantity="other">%d個喜歡</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="shares">
|
||||||
|
<item quantity="other">%d個分享</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="posted_on">在%1$s發表</string>
|
||||||
|
<string name="NoCommentsToShow">此貼文下沒有回覆……</string>
|
||||||
|
<string name="empty_comment">回覆内容不能為空!</string>
|
||||||
|
<string name="share_image">分享相片</string>
|
||||||
|
<string name="comment_error">回覆失敗!</string>
|
||||||
|
<string name="comment_posted">回覆「%1$s」已發表!</string>
|
||||||
|
<string name="comment_verb">回覆</string>
|
||||||
|
<string name="comment_noun">回覆</string>
|
||||||
|
<plurals name="number_comments">
|
||||||
|
<item quantity="other">%d則回覆</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="add_comment">發表回覆</string>
|
||||||
|
<string name="submit_comment">發表回覆</string>
|
||||||
|
<string name="post_is_album">這則貼文是一個集合</string>
|
||||||
|
<string name="post_is_video">這則貼文是一支影片</string>
|
||||||
|
<plurals name="nb_posts">
|
||||||
|
<item quantity="other">%d
|
||||||
|
\n貼文</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="nb_followers">
|
||||||
|
<item quantity="other">%d
|
||||||
|
\n追蹤者</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="edit">編輯</string>
|
||||||
|
<string name="save_image_failed">無法儲存相片</string>
|
||||||
|
<string name="save_image_success">相片成功存儲</string>
|
||||||
|
<string name="follow_status_failed">無法獲取追蹤狀態</string>
|
||||||
|
<string name="edit_link_failed">無法打開編輯頁面</string>
|
||||||
|
<string name="empty_feed">本來無一物,何處惹塵埃</string>
|
||||||
|
<string name="follow_button_failed">無法顯示追蹤按鈕</string>
|
||||||
|
<string name="follow_error">無法追蹤</string>
|
||||||
|
<string name="action_not_allowed">不允許此操作</string>
|
||||||
|
<string name="unfollow_error">無法取消追蹤</string>
|
||||||
|
<string name="access_token_invalid">這個access token無效</string>
|
||||||
|
<string name="default_nposts">-
|
||||||
|
\n貼文</string>
|
||||||
|
<string name="default_nfollowers">-
|
||||||
|
\n追蹤者</string>
|
||||||
|
<string name="default_nfollowing">-
|
||||||
|
\n追蹤中</string>
|
||||||
|
<string name="no_username">無使用者名稱</string>
|
||||||
|
<string name="follow">追蹤</string>
|
||||||
|
<string name="follow_requested">請求已送出</string>
|
||||||
|
<string name="dialog_message_cancel_follow_request">取消追蹤申請嗎?</string>
|
||||||
|
<string name="edit_profile">編輯個人資料</string>
|
||||||
|
<string name="search">搜尋</string>
|
||||||
|
<string name="posts">貼文</string>
|
||||||
|
<string name="accounts">使用者</string>
|
||||||
|
<string name="hashtags">推標</string>
|
||||||
|
<string name="media_upload_completed">媒體上載成功</string>
|
||||||
|
<string name="media_upload_failed">媒體上載失敗,重新試試,或者檢查下網路</string>
|
||||||
|
<string name="posting_image_accessibility_hint">準備要貼出的相片</string>
|
||||||
|
<string name="retry">重試</string>
|
||||||
|
<string name="nothing_to_see_here">空無一物!</string>
|
||||||
|
<string name="about_pixeldroid">關於PixelDroid</string>
|
||||||
|
<string name="dependencies_licenses">依賴和許可</string>
|
||||||
|
<string name="about">關於</string>
|
||||||
|
<string name="post_title">%1$s的貼文</string>
|
||||||
|
<string name="collection_title">%1$s的合集</string>
|
||||||
|
<string name="followers_title">%1$s的追蹤者</string>
|
||||||
|
<string name="hashtag_title">#%1$s</string>
|
||||||
|
<string name="follows_title">%1$s的追蹤</string>
|
||||||
|
<string name="search_empty_error">搜尋的關鍵詞不能為空</string>
|
||||||
|
<string name="status_more_options">更多</string>
|
||||||
|
<string name="report">回報</string>
|
||||||
|
<string name="bookmark">書簽</string>
|
||||||
|
<string name="unbookmark">從書簽中移除</string>
|
||||||
|
<string name="share_link">分享連結</string>
|
||||||
|
<string name="reported">已報告貼文</string>
|
||||||
|
<string name="profile_picture">個人資料相片</string>
|
||||||
|
<string name="open_drawer_menu">打開抽屜菜單</string>
|
||||||
|
<string name="discover">發現</string>
|
||||||
|
<string name="something_went_wrong">發生錯誤……</string>
|
||||||
|
<string name="panda_pull_to_refresh_to_try_again">這隻熊貓不開心。下拉重載以重試。</string>
|
||||||
|
<string name="redraft">重擬</string>
|
||||||
|
<string name="redraft_dialog_cancel">如果你取消這次重擬,原貼文將會被從你的賬戶中移除。繼續嗎?</string>
|
||||||
|
<string name="delete">刪除</string>
|
||||||
|
<string name="delete_dialog">刪除這則貼文嗎?</string>
|
||||||
|
<string name="language">語言</string>
|
||||||
|
<string name="help_translate">幫助將PixelDroid翻譯成你的語言吧:</string>
|
||||||
|
<string name="issues_contribute">報告問題或是給這個軟體貢獻:</string>
|
||||||
|
<string name="redraft_post_failed_error">無法重擬這則貼文,錯誤%1$d</string>
|
||||||
|
<string name="redraft_post_failed_io_except">無法重擬貼文,檢查你的網路?</string>
|
||||||
|
<string name="delete_post_failed_error">無法刪除這則貼文,錯誤%1$d</string>
|
||||||
|
<string name="delete_post_failed_io_except">無法刪除這則貼文,檢查你的網路?</string>
|
||||||
|
<string name="bookmark_post_failed_error">無法對此貼文進行書簽操作,錯誤%1$d</string>
|
||||||
|
<string name="file_not_found">沒找到檔案%1$s</string>
|
||||||
|
<string name="notifications_settings">通知設定</string>
|
||||||
|
<string name="notifications_settings_summary">管理你想接收到的通知</string>
|
||||||
|
<string name="login_notifications">無法獲取最新通知</string>
|
||||||
|
<string name="no_storage_permission">存儲權限未獲取。如果想查看縮圖的話請在設定中允許該權限</string>
|
||||||
|
<string name="play_video">播放影片</string>
|
||||||
|
<string name="encode_error">編碼錯誤</string>
|
||||||
|
<string name="encode_success">編碼成功!</string>
|
||||||
|
<string name="encode_progress">編碼%1$d%%</string>
|
||||||
|
<string name="analyzing_stabilization">穩定性分析%1$d%%</string>
|
||||||
|
<string name="still_encoding">有影片還在編碼。請在上載前等待編碼完成</string>
|
||||||
|
<string name="new_post_shortcut_long">建立新貼文</string>
|
||||||
|
<string name="new_post_shortcut_short">新貼文</string>
|
||||||
|
<string name="follow_request">%1$s請求追蹤</string>
|
||||||
|
<string name="home_feed">主頁面</string>
|
||||||
|
<string name="search_discover_feed">搜尋</string>
|
||||||
|
<string name="create_feed">建立</string>
|
||||||
|
<string name="notifications_feed">更新</string>
|
||||||
|
<string name="public_feed">公共</string>
|
||||||
|
<string name="accentColorTitle">色調</string>
|
||||||
|
<string name="color_choice_button">選擇這個色調</string>
|
||||||
|
<string name="color_chosen">色調已選擇</string>
|
||||||
|
<string name="profile_error">無法載入資料</string>
|
||||||
|
<string name="from_other_domain">來自%1$s</string>
|
||||||
|
<string name="add_images_error">加入相片時發生錯誤</string>
|
||||||
|
<string name="post_preview">貼文預覽</string>
|
||||||
|
<string name="description_template_summary">在新貼文的描述中預填入</string>
|
||||||
|
<string name="description_template">描述模板</string>
|
||||||
|
<string name="explore_accounts">探索這個實例上熱門的使用者</string>
|
||||||
|
<string name="popular_accounts">熱門使用者</string>
|
||||||
|
<string name="explore_hashtags">探索此實例中急上升的推標</string>
|
||||||
|
<string name="trending_hashtags">急上升推標</string>
|
||||||
|
<string name="daily_trending">瀏覽每日熱門貼文</string>
|
||||||
|
<string name="trending_posts">熱門貼文</string>
|
||||||
|
<string name="grid_view">網格視圖</string>
|
||||||
|
<string name="feed_view">饋送視圖</string>
|
||||||
|
<string name="bookmarks">書簽</string>
|
||||||
|
<string name="collections">合集</string>
|
||||||
|
<string name="delete_collection">刪除合集</string>
|
||||||
|
<string name="collection_add_post">加入貼文</string>
|
||||||
|
<string name="collection_remove_post">移除貼文</string>
|
||||||
|
<string name="add_to_collection">選取貼文以新增</string>
|
||||||
|
<string name="delete_from_collection">選取貼文已移除</string>
|
||||||
|
<string name="added_post_to_collection">已新增貼文到合集中</string>
|
||||||
|
<string name="error_add_post_to_collection">無法將貼文新增至合集中</string>
|
||||||
|
<string name="error_remove_post_from_collection">無法將貼文從合集中移除</string>
|
||||||
|
<string name="removed_post_from_collection">已將貼文從合集中移除</string>
|
||||||
|
<plurals name="replies_count">
|
||||||
|
<item quantity="other">%d則回覆</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="save">保存</string>
|
||||||
|
<string name="more_profile_settings">更多資料設定</string>
|
||||||
|
<string name="private_account">私密使用者</string>
|
||||||
|
<string name="your_bio">你的自述</string>
|
||||||
|
<string name="your_name">你的名字</string>
|
||||||
|
<string name="profile_save_changes">不保存而退出嗎?</string>
|
||||||
|
<string name="fetching_profile">獲取你的資料……</string>
|
||||||
|
<string name="saving_profile">存儲你的資料中</string>
|
||||||
|
<string name="profile_saved">更改已保存!</string>
|
||||||
|
<string name="error_profile">發生錯誤。點按重試</string>
|
||||||
|
<string name="change_profile_picture">修改你的資料相片</string>
|
||||||
|
<string name="switch_accounts">切換使用者</string>
|
||||||
|
<string name="summary_always_show_nsfw">敏感媒體將不會被模糊,默認展示。</string>
|
||||||
|
<string name="contains_nsfw">包含敏感媒體</string>
|
||||||
|
<string name="story_image">故事相片</string>
|
||||||
|
<string name="replyToStory">回覆%1$s</string>
|
||||||
|
<string name="error_fetch_story">獲取旋轉視圖的時候出了點問題</string>
|
||||||
|
<string name="sent_reply_story">傳送了回覆</string>
|
||||||
|
<string name="add_story">新增故事</string>
|
||||||
|
<string name="story_could_not_see">錯誤:不能將故事標注為已看過</string>
|
||||||
|
<string name="story_pause">開始或暫停故事</string>
|
||||||
|
<string name="my_story">我的故事</string>
|
||||||
|
<string name="type_story">故事</string>
|
||||||
|
<string name="type_post">貼文</string>
|
||||||
|
<string name="continue_post_creation">繼續</string>
|
||||||
|
<string name="extraneous_pictures_stories">第一張相片后的相片已刪除,不過可以透過切換回創建貼文來恢復</string>
|
||||||
|
<string name="story_duration">故事時長</string>
|
||||||
|
<string name="write_permission_download_pic">你需要允許存儲空間權限才能保存相片!</string>
|
||||||
|
<string name="unfollow">取消追蹤</string>
|
||||||
|
<string name="cw_nsfw_hidden_media_n_click_to_show">敏感媒體
|
||||||
|
\n (點按閲覽)</string>
|
||||||
|
<string name="project_website">項目網站:https://pixeldroid.org</string>
|
||||||
|
<plurals name="nb_following">
|
||||||
|
<item quantity="other">%d
|
||||||
|
\n追蹤中</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="optional_report_comment">給管理員的附加資訊</string>
|
||||||
|
<string name="new_collection_link_failed">無法打開合集創建頁面</string>
|
||||||
|
<string name="license_info">PixelDroid是一個免費且開源的軟體,由GNU General Public License (version 3或更新版)授權</string>
|
||||||
|
<string name="report_target">報告@%1$s的貼文</string>
|
||||||
|
<string name="report_error">無法傳送報告</string>
|
||||||
|
<string name="redraft_dialog_launch">重擬這則貼文將允許你編輯它的相片和描述,但會刪除現在所有的回覆和按讚。繼續?</string>
|
||||||
|
<string name="notification_thumbnail">在這則貼文通知中的縮圖</string>
|
||||||
|
<string name="private_account_explanation">當你的使用者為私密時,只有你認可的使用者才能看到你在Pixelfed上的相片和影片。現存的追蹤者不受影響。</string>
|
||||||
|
<string name="mascot_description">一個展示了一隻紅熊貓(Pixelfed的吉祥物)用手機的相片</string>
|
||||||
|
<string name="use_dynamic_color">用系統的動態顔色</string>
|
||||||
|
<string name="story_reply_error">發表回覆的時候出了點問題</string>
|
||||||
|
<string name="bookmark_post_failed_io_except">無法對此貼文進行書簽操作,檢查你的網路?</string>
|
||||||
|
<string name="status_notification">%1$s建立了一個貼文</string>
|
||||||
|
<string name="no_camera_permission">相機權限未獲取。如果想用攝影功能的話請在設定中允許該權限</string>
|
||||||
|
<string name="accentColorSummary">選擇一個色調</string>
|
||||||
|
<string name="explore_posts">瀏覽隨機貼文</string>
|
||||||
|
<string name="delete_collection_warning">你確定要刪除此合集嗎?</string>
|
||||||
|
</resources>
|
|
@ -153,7 +153,10 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||||
<string name="share_image">Share Image</string>
|
<string name="share_image">Share Image</string>
|
||||||
<string name="comment_error">Comment error!</string>
|
<string name="comment_error">Comment error!</string>
|
||||||
<string name="comment_posted">"Comment: %1$s posted!"</string>
|
<string name="comment_posted">"Comment: %1$s posted!"</string>
|
||||||
<string name="comment">Comment</string>
|
<!-- This is shown on the button, it is a verb and refers to the action of commenting -->
|
||||||
|
<string name="comment_verb">Comment</string>
|
||||||
|
<!-- This is shown in the text field as a hint, it is a noun -->
|
||||||
|
<string name="comment_noun">Comment</string>
|
||||||
<plurals name="number_comments">
|
<plurals name="number_comments">
|
||||||
<item quantity="one">%d comment</item>
|
<item quantity="one">%d comment</item>
|
||||||
<item quantity="other">%d comments</item>
|
<item quantity="other">%d comments</item>
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
android:id="@+id/first"
|
||||||
|
app:constraintSetStart="@id/start"
|
||||||
|
app:constraintSetEnd="@id/hideProfile">
|
||||||
|
<OnSwipe
|
||||||
|
app:touchAnchorId="@+id/profile"
|
||||||
|
app:touchAnchorSide="top"
|
||||||
|
app:dragDirection="dragUp" />
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
android:id="@+id/second"
|
||||||
|
app:constraintSetStart="@id/hideProfile"
|
||||||
|
app:constraintSetEnd="@id/hideBars">
|
||||||
|
<OnSwipe
|
||||||
|
app:touchAnchorId="@+id/profileTabs"
|
||||||
|
app:touchAnchorSide="top"
|
||||||
|
app:dragDirection="dragUp" />
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<ConstraintSet android:id="@+id/start">
|
||||||
|
<Constraint android:id="@id/top_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
</Constraint>
|
||||||
|
</ConstraintSet>
|
||||||
|
|
||||||
|
<ConstraintSet android:id="@+id/hideProfile">
|
||||||
|
<Constraint android:id="@id/profile"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/top_bar"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
</Constraint>
|
||||||
|
<Constraint android:id="@id/profileTabs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_bar"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
</Constraint>
|
||||||
|
|
||||||
|
<Constraint android:id="@id/top_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
</Constraint>
|
||||||
|
</ConstraintSet>
|
||||||
|
|
||||||
|
<ConstraintSet android:id="@+id/hideBars">
|
||||||
|
<Constraint android:id="@id/profile"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/top_bar"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
</Constraint>
|
||||||
|
|
||||||
|
<Constraint android:id="@id/profileTabs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
</Constraint>
|
||||||
|
<Constraint android:id="@id/top_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/profileTabs"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
</Constraint>
|
||||||
|
</ConstraintSet>
|
||||||
|
</MotionScene>
|
12
build.gradle
12
build.gradle
|
@ -6,7 +6,7 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
classpath 'com.android.tools.build:gradle:8.3.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
@ -16,6 +16,7 @@ buildscript {
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.google.devtools.ksp' version '1.9.20-1.0.14' apply false
|
id 'com.google.devtools.ksp' version '1.9.20-1.0.14' apply false
|
||||||
|
id("com.google.dagger.hilt.android") version "2.50" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
@ -23,15 +24,6 @@ allprojects {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
//noinspection JcenterRepositoryObsolete
|
|
||||||
jcenter {
|
|
||||||
content {
|
|
||||||
// info.androidhive:imagefilters is only available in JCenter
|
|
||||||
//TODO remove JCenter repo:
|
|
||||||
// see issue https://gitlab.shinice.net/pixeldroid/PixelDroid/-/issues/278
|
|
||||||
includeGroup("info.androidhive")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
Bug fix for opening album full-screen crashing
|
||||||
|
|
||||||
|
Use hardware acceleration (OpenGL! Shaders!) for image editing. Currently just trying to mimic the previous implementation, but this will provide building blocks for much cooler things later on :)
|
||||||
|
|
||||||
|
Tell us if something doesn't work right! (email, Mastodon)
|
||||||
|
|
||||||
|
There are also translation updates in this release :)
|
|
@ -0,0 +1 @@
|
||||||
|
Fix bug that was breaking the image editing feature in release mode
|
|
@ -0,0 +1,3 @@
|
||||||
|
* Fix crash when sharing an image from gallery etc to the app
|
||||||
|
|
||||||
|
* Fix permission issue causing images to throw a permission denied error
|
|
@ -0,0 +1 @@
|
||||||
|
Split APKs per CPU architecture, makes the APKs a lot smaller (100MB to 25MB)
|
|
@ -0,0 +1,3 @@
|
||||||
|
Try again to split the apks
|
||||||
|
|
||||||
|
Update dependencies
|
|
@ -0,0 +1,3 @@
|
||||||
|
* Montage vidéo ! Supprimez le son, découper les vidéos
|
||||||
|
* Ouvrez les images en plein écran et zoomez dessus :)
|
||||||
|
* Mises à jour des traductions
|
|
@ -0,0 +1 @@
|
||||||
|
Correctif pour les plantages dans l'activité d'édition qui ne se produisaient qu'en mode release
|
|
@ -0,0 +1 @@
|
||||||
|
Ajout de la traduction en hongrois (Magyar) à l'application. Merci à Balázs :)
|
|
@ -0,0 +1,4 @@
|
||||||
|
* Ajout de règles gson proguard pour corriger les crashs sur les instances Mastodon
|
||||||
|
* Ajout de thèmes de couleur avec 4 thèmes différents
|
||||||
|
* Passage à Material 3
|
||||||
|
* Améliorer la cohérence de l'interface utilisateur
|
|
@ -0,0 +1,7 @@
|
||||||
|
* Commentaires : leur ouverture permet de voir les réponses et l'avatar de l'auteur, l'aimer, etc
|
||||||
|
* MAJ des traductions. Merci aux traducteurs :) Aidez à la traduction sur weblate.pixeldroid.org
|
||||||
|
* Sécurité : altération des dépendances vérifiée, refus des connexions HTTP
|
||||||
|
* Utilisation du User Agent "PixelDroid"
|
||||||
|
* Suppression des chaînes codées en dur, tout est maintenant traduisible
|
||||||
|
* Quelques améliorations du code
|
||||||
|
* Correction de la sauvegarde des images et d'un crash sur des instances Mastodon
|
|
@ -0,0 +1,11 @@
|
||||||
|
* Ajout d'un graphique d'erreur personnalisé panda rouge
|
||||||
|
|
||||||
|
* Autoriser le recadrage libre dans l'édition d'image
|
||||||
|
|
||||||
|
* Amélioration des métadonnées pour F-Droid
|
||||||
|
|
||||||
|
* Mises à jour des traductions
|
||||||
|
|
||||||
|
* Mise à jour des dépendances
|
||||||
|
|
||||||
|
* Correction de bugs
|
|
@ -0,0 +1,9 @@
|
||||||
|
* Métadonnées des photos supprimées avant de les envoyer
|
||||||
|
* Favoris !
|
||||||
|
* Voir un profil comme un flux ou une grille
|
||||||
|
* Définir un modèle pour vos descriptions
|
||||||
|
* Badge sur l'icône de notification pour indiquer les nouvelles notifications
|
||||||
|
* Fonctionnalités d'édition vidéo : recadrer, changer la vitesse, stabilisation
|
||||||
|
* Implémentation de couleurs dynamiques : PixelDroid peut suive la couleur de votre arrière-plan (Android 12 et plus)
|
||||||
|
* Correction de bugs
|
||||||
|
* Mise à jour des traductions
|
|
@ -0,0 +1,7 @@
|
||||||
|
* Mises à jour des traductions : Merci aux traducteurs ❤️ ! Allez sur notre weblate pour nous aider à la traduction.
|
||||||
|
|
||||||
|
#️⃣ Prise en charge des hashtags. Possibilité de parcourir les hashtags, au lieu d'afficher un "toast" message 😜
|
||||||
|
|
||||||
|
* Cliquez sur l'onglet pour revenir au début. Une fois dans la page, il suffit de cliquer sur l'onglet dans lequel vous vous trouvez pour revenir en haut :)
|
||||||
|
|
||||||
|
* Essai de correction d'un bug qui faisait planter l'application. Merci pour vos rapports de crash ! ❤️
|
|
@ -0,0 +1,4 @@
|
||||||
|
- Suppression et reformulation des messages existants
|
||||||
|
- Les collections d'articles peuvent désormais être visualisées et modifiées.
|
||||||
|
- La création d'un message se fait désormais en deux étapes, avec une nouvelle prise en charge des éléments suivants : sensibilité NSFW, changement de compte, etc
|
||||||
|
- Beaucoup d'autres changements et améliorations :)
|
|
@ -0,0 +1,4 @@
|
||||||
|
* Messages d'avertissement moins agressifs si vous désactivez la caméra ou les permissions de fichiers
|
||||||
|
* Mise à jour des traductions
|
||||||
|
* Correction de l'interruption de l'envoi de vidéos
|
||||||
|
* Amélioration de la gestion des erreurs
|
|
@ -0,0 +1,4 @@
|
||||||
|
* Prise en charge des Stories !
|
||||||
|
* Mises à jour des dépendances
|
||||||
|
* Accélération matériel pour l'encodage vidéo
|
||||||
|
* Beaucoup de travail en coulisse :)
|
|
@ -0,0 +1 @@
|
||||||
|
Corrections de bugs et améliorations ;)
|
|
@ -0,0 +1,7 @@
|
||||||
|
Correction d'un bug lors de l'ouverture d'un album en plein écran.
|
||||||
|
|
||||||
|
Utiliser l'accélération matérielle (OpenGL ! Shaders !) pour l'édition d'images. Pour l'instant, nous essayons juste d'imiter l'implémentation précédente, mais cela fournira des blocs de construction pour des choses bien plus cool par la suite :)
|
||||||
|
|
||||||
|
Dites-nous si quelque chose ne fonctionne pas correctement ! (email, Mastodon)
|
||||||
|
|
||||||
|
Il y a aussi des mises à jour de traduction dans cette version :)
|
|
@ -0,0 +1,7 @@
|
||||||
|
* Ajout d'une langue (Malayalam)
|
||||||
|
|
||||||
|
* Correction de quelques mauvaises réponses de l'API
|
||||||
|
|
||||||
|
* Mise à jour des dépendances
|
||||||
|
|
||||||
|
* Mise à jour des traductions
|
|
@ -0,0 +1 @@
|
||||||
|
Correction d'un bug qui cassait la fonction d'édition d'images en mode release
|
|
@ -0,0 +1,3 @@
|
||||||
|
* Correction d'un crash lors du partage d'une image (de la galerie, etc.) vers l'application
|
||||||
|
|
||||||
|
* Correction d'un problème de permission qui causait de la part des images une erreur "permission refusée"
|
|
@ -0,0 +1 @@
|
||||||
|
Séparation des APKs par architecture CPU, rendant les APKs beaucoup plus petits (passage de 100Mo à 25Mo)
|
|
@ -0,0 +1,3 @@
|
||||||
|
Nouvelle tentative de séparation des apks
|
||||||
|
|
||||||
|
Mise à jour des dépendances
|
|
@ -0,0 +1,7 @@
|
||||||
|
- Support des notifications ! Encore un peu rudimentaire :)
|
||||||
|
- Correction : rotation EXIF ignorée, photos tournées dans le mauvais sens
|
||||||
|
- Correction #300
|
||||||
|
- Correction : navigateurs webview affichant une erreur à la connexion quand l'URL contenait des espaces
|
||||||
|
- Correction : vidage des caches, flux vides aux lancements, performance, photos de profil et appareil photo en erreur après changement d'onglet
|
||||||
|
- Traduction : ajout du Tchèque et mise à jour des autres langues
|
||||||
|
- Dépendances mises à jour
|
|
@ -0,0 +1,5 @@
|
||||||
|
* Mises à jour des traductions
|
||||||
|
* Amélioration de l'affichage des informations sur les licences
|
||||||
|
* Correction des problèmes de permission dans l'onglet caméra
|
||||||
|
* Correction de l'analyseur de liens
|
||||||
|
* Correction de la vue découverte (peut nécessiter des mises à jour de l'instance)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue