This commit is contained in:
dhk2 2021-01-16 13:16:21 -08:00
commit 4f60902c90
220 changed files with 5718 additions and 2258 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
Dockerfile

163
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,163 @@
stages:
- environment
- build
- test
- internal
- alpha
- beta
- production
- stop
.updateContainerJob:
image: docker:stable
stage: environment
services:
- docker:dind
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true
- docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
tags:
- shell
updateContainer:
extends: .updateContainerJob
only:
changes:
- Dockerfile
ensureContainer:
extends: .updateContainerJob
allow_failure: true
before_script:
- "mkdir -p ~/.docker && echo '{\"experimental\": \"enabled\"}' > ~/.docker/config.json"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
# Skip update container `script` if the container already exists
# via https://gitlab.com/gitlab-org/gitlab-ce/issues/26866#note_97609397 -> https://stackoverflow.com/a/52077071/796832
- docker manifest inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG > /dev/null && exit || true
.build_job:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
stage: build
before_script:
# We store this binary file in a variable as hex with this command, `xxd -p android-signing-keystore.jks > jks.txt` (remove all \n)
# Then we convert the hex back to a binary file
- pwd
- echo "$signing_jks_file_hex" | xxd -r -p - > android-signing-keystore.jks
- md5sum android-signing-keystore.jks
# get next version from latest changelog
- "export VERSION_CODE=`ls -f ./fastlane/metadata/android/en-US/changelogs | cut -d_ -f3 | sort -n | tail -1 | rev | cut -c5- | rev` && echo $VERSION_CODE"
# We add 200 to get this high enough above current versionCodes that are published
# - "export VERSION_CODE=$((200 + $CI_PIPELINE_IID)) && echo $VERSION_CODE"
- "export VERSION_SHA=`echo ${CI_COMMIT_SHA:0:8}` && echo $VERSION_SHA"
- "export VERSION_NAME=${VERSION_CODE:0:1}.${VERSION_CODE:1:1}.${VERSION_CODE:2} && echo $VERSION_NAME"
after_script:
- rm -f android-signing-keystore.jks || true
artifacts:
paths:
- app/build/outputs
tags:
- docker
buildDebug:
extends: .build_job
script:
- bundle exec fastlane buildDebug
buildRelease:
extends: .build_job
script:
- bundle exec fastlane buildRelease
environment:
name: production
only:
- /^v[0-9]*\.[0-9]*\.[0-9]*$/i
except:
- branches
testDebug:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
stage: test
dependencies:
- buildDebug
script:
- bundle exec fastlane test
tags:
- docker
publishGithub:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
stage: internal
dependencies:
- buildRelease
when: manual
script:
- "export VERSION_CODE=`ls -f ./fastlane/metadata/android/en-US/changelogs | cut -d_ -f3 | sort -n | tail -1 | rev | cut -c5- | rev` && echo $VERSION_CODE"
- "export VERSION_SHA=`echo ${CI_COMMIT_SHA:0:8}` && echo $VERSION_SHA"
- "export VERSION_NAME=${VERSION_CODE:0:1}.${VERSION_CODE:1:1}.${VERSION_CODE:2} && echo $VERSION_NAME"
- ci-scripts/make-github-release.sh
tags:
- docker
only:
- /^v[0-9]*\.[0-9]*\.[0-9]*$/i
publishInternal:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
stage: internal
dependencies:
- buildRelease
when: manual
before_script:
- echo $google_play_service_account_api_key_json > ../google_play_api_key.json
- md5sum /builds/sschueller/google_play_api_key.json
after_script:
- rm -f ../google_play_api_key.json
script:
- bundle exec fastlane internal
tags:
- docker
only:
- /^v[0-9]*\.[0-9]*\.[0-9]*$/i
.promote_job:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
when: manual
dependencies: []
before_script:
- echo $google_play_service_account_api_key_json > ../google_play_api_key.json
- md5sum ../google_play_api_key.json
after_script:
- rm -f ../google_play_api_key.json
promoteAlpha:
extends: .promote_job
stage: alpha
script:
- bundle exec fastlane promote_internal_to_alpha
tags:
- docker
only:
- /^v[0-9]*\.[0-9]*\.[0-9]*$/i
promoteBeta:
extends: .promote_job
stage: beta
script:
- bundle exec fastlane promote_alpha_to_beta
tags:
- docker
only:
- /^v[0-9]*\.[0-9]*\.[0-9]*$/i
promoteProduction:
extends: .promote_job
stage: production
# We only allow production promotion on `master` because
# it has its own production scoped secret variables
only:
- /^v[0-9]*\.[0-9]*\.[0-9]*$/i
script:
- bundle exec fastlane promote_beta_to_production
tags:
- docker

View File

@ -1,3 +1,52 @@
### Version 1.0.51 Tag: v1.0.51 (2021-01-14)
- fixed default app language on first start (@kosharskiy)
- Settings screen translations uk and ru languages (@kosharskiy)
- cleanup app/build.gradle file (@kosharskiy)
- fixed video meta data display issue (@kosharskiy)
- updated translations
### Version 1.0.50 Tag: v1.0.50 (2020-11-22)
- add support for disabling SSL
- translations
### Version 1.0.49 Tag: v1.0.49 (2020-09-26)
- add support of hypertext redirection in description (@freeboub)
- various crash fixes (@freeboub)
- avoid going to pip when leaving the app due to share button (@freeboub)
- Add ability to filter server list (@freeboub)
- Refactor Toast error management to split network error (@freeboub)
- keep video aspect ratio for pip (@freeboub)
- navigation bar was not restored when leaving landscape mode (@freeboub)
### Version 1.0.48 Tag: v1.0.48 (2020-09-26)
- f-droid release to fix auto deployment
### Version 1.0.47 Tag: v1.0.47 (2020-07-10)
* Authentication refresh
### Version 1.0.46 Tag: v1.0.46 (2020-07-08)
* Revert broken auth
### Version 1.0.45 Tag: v1.0.45 (2020-07-08)
* Added token refresh
### Version 1.0.44 Tag: v1.0.44 (2020-07-05)
* Completed implementation of Likes & Dislikes (@Poslovitch)
* Added preview of the current playback speed and video quality in the VideoOptionsFragment (@Poslovitch)
* Lots of code cleanup
* Various translations
### Version 1.0.43 Tag: v1.0.43 (2020-07-04)
* Fix back button issue
### Version 1.0.42 Tag: v1.0.42 (2020-07-04)
* Added appbar at the top of the SettingsActivity (@Poslovitch)
* Improved and added some French translations (@Poslovitch)
* Removed translations for untranslatable strings (@Poslovitch)
* Add stop button to expanded notification, and stop and switch to audio in video window (@dhk2)
* More data in Server search
* VideoList timestamp fix
### Version 1.0.41 Tag: v1.0.41 (2020-06-28)
* Floating window player controls fix (@dhk2)
* Updated app icons

16
DEPLOYMENT.md Normal file
View File

@ -0,0 +1,16 @@
## Internal deployment notes
1. merge pull-requests on github into develop
2. Locally switch to develop
3. Pull github develop
4. Pull weblate develop
5. Add change logs (fastlane/metadata/android/en-US/changelogs/XXX.txt)
6. Run ci-script/update-changelog.sh
7. Push to gitlab
8. Merge request into master and merge
9. Add Release Tag on master branch
10. Release to play store
11. Wait for gitlab -> github sync
12. Run publishGithub

45
Dockerfile Normal file
View File

@ -0,0 +1,45 @@
FROM gradle:6.1.1-jdk8
ENV ANDROID_SDK_URL https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
ENV ANDROID_SDK_CHECKSUM 444e22ce8ca0f67353bda4b85175ed3731cae3ffa695ca18119cbacef1c1bea0
ENV ANDROID_BUILD_TOOLS_VERSION 29.0.3
ENV ANDROID_HOME /usr/local/android-sdk-linux
ENV ANDROID_VERSION 29
ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
RUN mkdir "$ANDROID_HOME" .android && \
cd "$ANDROID_HOME" && \
curl -o sdk.zip $ANDROID_SDK_URL && \
echo "${ANDROID_SDK_CHECKSUM} sdk.zip" | sha256sum -c - && \
unzip sdk.zip && \
rm sdk.zip
RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses
RUN $ANDROID_HOME/tools/bin/sdkmanager --update
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
"platforms;android-${ANDROID_VERSION}" \
"platform-tools"
# install OS packages
RUN apt-get --quiet update --yes
# Installing build tools
RUN apt-get update && \
apt-get install -y \
build-essential \
ruby \
jq \
ruby-dev
# We use this for xxd hex->binary
RUN apt-get --quiet install --yes vim-common
# install FastLane
COPY Gemfile.lock .
COPY Gemfile .
RUN gem update --system 3.0.8 # https://github.com/rubygems/rubygems/issues/3068
RUN gem install bundler
RUN bundle install
# at least 1.5G memory is required for the gitlab runner to succeed
#RUN echo "org.gradle.jvmargs=-Xmx1536m" >> local.properties

3
Gemfile Normal file
View File

@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

178
Gemfile.lock Normal file
View File

@ -0,0 +1,178 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.353.0)
aws-sdk-core (3.104.3)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.36.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.76.0)
aws-sdk-core (~> 3, >= 3.104.1)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.3)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.1)
rake (~> 13.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.0.0)
excon (0.76.0)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.0)
fastlane (2.155.3)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-api-client (0.38.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.3.3)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
google-cloud-storage (1.27.0)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.13.1)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.3.1)
jwt (2.2.1)
memoist (0.16.2)
mini_magick (4.10.1)
mini_mime (1.0.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.0)
os (1.1.1)
plist (3.5.0)
public_suffix (4.0.5)
rake (13.0.1)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.0)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.17.1)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
BUNDLED WITH
2.1.4

View File

@ -4,7 +4,7 @@
<p align="center">
<a href="https://github.com/sschueller/peertube-android/releases/latest" alt="GitHub release"><img src="https://img.shields.io/github/release/sschueller/peertube-android.svg" ></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://www.gnu.org/licenses/agpl-3.0" alt="License: AGPLv3"><img src="https://img.shields.io/badge/License-AGPL%20v3-blue.svg"></a>
<a href="https://f-droid.org/de/packages/net.schueller.peertube/" alt="F-Droid release"><img src="https://img.shields.io/f-droid/v/net.schueller.peertube.svg"></a>
<a href="https://hosted.weblate.org/projects/peertube/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/peertube/-/svg-badge.svg"></a>
<a href="https://www.bountysource.com/teams/peertube-android" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/peertube-android/activity.svg?colorB=cd201f"></a>
@ -12,8 +12,9 @@
</p>
## Screenshots
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png)
## Description
PeerTube is a federated video streaming platform that is community-owned and ad-free, with no vendor lock-in. This client allows you to watch and browse videos on a server of your choice in the PeerTube network.
@ -27,6 +28,13 @@ Please note this is app is in beta and is still missing a lot of features.
* Beta Test on Google Play: https://play.google.com/store/apps/details?id=net.schueller.peertube
* F-Droid: https://f-droid.org/packages/net.schueller.peertube/
## Releases
Release Tags are automatically built and deployed to the play store but not released to production right away.
To use the latest tagged release download it from github or fdroid (~1 day delay).
## Reproducible Builds
Reproducible builds currently does not work.
## Help Translate
* https://hosted.weblate.org/projects/peertube/
@ -47,11 +55,11 @@ Please note this is app is in beta and is still missing a lot of features.
* Video speed selection
* Video quality selection
* Server selection
* Video overlay play and draggable video window
## Coming soon
* Video Playback via WebRTC
* Video overlay play and draggable video window
* Comment videos
* Report Videos
* User / Channel Overview Page
@ -65,4 +73,4 @@ Whether you have ideas, translations, design changes, code cleaning, or real hea
<noscript><a href="https://liberapay.com/sschueller/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
Bitcoin: 1LoTXo728HzYTtyfbkaf5ewSRvu8ABTDPm
Bitcoin: 1LoTXo728HzYTtyfbkaf5ewSRvu8ABTDPm

62
REPRODUCIBLE_BUILDS.md Normal file
View File

@ -0,0 +1,62 @@
# Reproducible Builds
Note: This does not work at this time
## Install Docker
Download and install [Docker](https://www.docker.com/).
## Check your Thorium app version and build timestamp
1. Open the Thorium app
2. Go to Settings
3. Check the app version listed under About 'Version' (e.g., 1.0.44), and record its value to be used later
4. Check the build timestamp under About 'Build Time' (e.g., 1593942384524), and record its value to be used later
## Download the App open-source code
1. Make sure you have `git` installed
2. Clone the Github repository
3. Checkout the Tag that corresponds to the version of your Thorium app (e.g., 1.0.44)
```shell
git clone https://github.com/sschueller/peertube-android ~/peertube-android
cd ~/peertube-android
git checkout v1.0.44
```
## Build the project using Docker
1. Build a Docker Image with the required Android Tools
2. Build the App in the Docker Container while specifying the build timestamp that was recorded earlier (e.g., 1593942384524)
3. Copy the freshly-built APK
```shell
cd ~/peertube-android
docker build -t thorium-builder .
docker run --rm -v ~/Private/peertube:/home/peertube -w /home/peertube thorium-builder gradle assembleRelease -PkeystorePassword=securePassword -PkeyAliasPassword=securePassword -PkeystoreFile=build.keystore -PbuildTimestamp=1593973044091
cp app/build/outputs/apk/release/app-release-unsigned.apk thorium-built.apk
```
## Extract the Play Store APK from your phone
1. Make sure you have `adb` installed
2. Connect your phone to your computer
3. Extract the APK from the phone
```shell
cd ~/peertube-android
adb shell pm path net.schueller.peertube
adb pull /data/app/net.schueller.peertube-mCeISw_AujlMBHyPfVhdSg==/base.apk thorium-store.apk
```
## Compare the two files
1. Make sure you have `python` installed
2. Use the `apkdiff` script to compare the APKs
```shell
cd ~/peertube-android
./apkdiff.py thorium-built.apk thorium-store.apk
```

66
apkdiff.py Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
# Taken from https://github.com/DrKLO/Telegram/blob/master/apkdiff.py on June 4th, 2020
import sys
from zipfile import ZipFile
def compareFiles(first, second):
while True:
firstBytes = first.read(4096);
secondBytes = second.read(4096);
if firstBytes != secondBytes:
return False
if firstBytes == b"":
break
return True
def compare(first, second):
FILES_TO_IGNORE = ["META-INF/MANIFEST.MF", "META-INF/CERT.RSA", "META-INF/CERT.SF"]
firstZip = ZipFile(first, 'r')
secondZip = ZipFile(second, 'r')
firstList = list(filter(lambda firstInfo: firstInfo.filename not in FILES_TO_IGNORE, firstZip.infolist()))
secondList = list(filter(lambda secondInfo: secondInfo.filename not in FILES_TO_IGNORE, secondZip.infolist()))
if len(firstList) != len(secondList):
print("APKs has different amount of files (%d != %d)" % (len(firstList), len(secondList)))
return False
for firstInfo in firstList:
found = False
for secondInfo in secondList:
if firstInfo.filename == secondInfo.filename:
found = True
firstFile = firstZip.open(firstInfo, 'r')
secondFile = secondZip.open(secondInfo, 'r')
if compareFiles(firstFile, secondFile) != True:
print("APK file %s does not match" % firstInfo.filename)
return False
secondList.remove(secondInfo)
break
if found == False:
print("file %s not found in second APK" % firstInfo.filename)
return False
if len(secondList) != 0:
for secondInfo in secondList:
print("file %s not found in first APK" % secondInfo.filename)
return False
return True
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage: apkdiff <pathToFirstApk> <pathToSecondApk>")
sys.exit(1)
if sys.argv[1] == sys.argv[2] or compare(sys.argv[1], sys.argv[2]) == True:
print("APKs are the same!")
else:
print("APKs are different!")

View File

@ -1,19 +1,54 @@
apply plugin: 'com.android.application'
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
id 'kotlin-kapt'
}
ext.readProperty = { paramName -> readPropertyWithDefault(paramName, null) }
ext.readPropertyWithDefault = { paramName, defaultValue ->
if (project.hasProperty(paramName)) {
return project.getProperties().get(paramName)
} else {
Properties properties = new Properties()
if (project.rootProject.file('local.properties').exists()) {
properties.load(project.rootProject.file('local.properties').newDataInputStream())
}
if (properties.getProperty(paramName) != null) {
return properties.getProperty(paramName)
} else {
return defaultValue
}
}
}
// Try reading secrets from file
def secretsPropertiesFile = rootProject.file("secrets.properties")
def secretProperties = new Properties()
if (secretsPropertiesFile.exists()) {
secretProperties.load(new FileInputStream(secretsPropertiesFile))
}
// Otherwise read from environment variables, this happens in CI
else {
secretProperties.setProperty("signing_keystore_password", "${System.getenv('signing_keystore_password')}")
secretProperties.setProperty("signing_key_password", "${System.getenv('signing_key_password')}")
secretProperties.setProperty("signing_key_alias", "${System.getenv('signing_key_alias')}")
}
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "net.schueller.peertube"
minSdkVersion 21
targetSdkVersion 29
versionCode 1041
versionName "1.0.41"
versionCode Integer.valueOf(System.getenv("VERSION_CODE") ?: 1)
versionName System.getenv("VERSION_NAME") + "-" + System.getenv("VERSION_SHA")
buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ext {
libVersions = [
exoplayer: '2.11.6'
]
}
javaCompileOptions {
annotationProcessorOptions {
arguments = [
@ -22,56 +57,24 @@ android {
"room.expandProjection": "true"]
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Layouts and design
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'de.hdodenhof:circleimageview:3.0.0'
// font awesome
implementation "com.mikepenz:iconics-core:3.1.0"
implementation 'com.mikepenz:fontawesome-typeface:5.3.1.1@aar'
// http client / REST
implementation 'com.squareup.okhttp3:okhttp:4.3.1'
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
// image downloading and caching library
implementation 'com.squareup.picasso:picasso:2.71828'
// json decoder/encoder
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
// Torrents and WebRTC
implementation 'com.github.TorrentStream:TorrentStream-Android:2.6.1'
// implementation "com.github.TorrentStream:TorrentStreamServer-Android:1.0.1"
// implementation 'org.webrtc:google-webrtc:1.0.+'
// video player repo:jcenter()
implementation "com.google.android.exoplayer:exoplayer-core:$libVersions.exoplayer"
implementation "com.google.android.exoplayer:exoplayer-dash:$libVersions.exoplayer"
implementation "com.google.android.exoplayer:exoplayer-ui:$libVersions.exoplayer"
implementation "com.google.android.exoplayer:exoplayer-hls:$libVersions.exoplayer"
implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$libVersions.exoplayer"
implementation "com.google.android.exoplayer:extension-mediasession:$libVersions.exoplayer"
// testing
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
signingConfigs {
release {
// You need to specify either an absolute path or include the
// keystore file in the same directory as the build.gradle file.
storeFile file("../android-signing-keystore.jks")
storePassword "${secretProperties['signing_keystore_password']}"
keyAlias "${secretProperties['signing_key_alias']}"
keyPassword "${secretProperties['signing_key_password']}"
}
}
buildTypes {
release {
minifyEnabled false
testCoverageEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
compileOptions {
@ -82,28 +85,91 @@ android {
applicationVariants.all { variant ->
variant.resValue "string", "versionName", variant.versionName
}
buildFeatures{
viewBinding = true
}
}
def room_version = "2.2.6"
def lifecycleVersion = '2.2.0'
def exoplayer = '2.12.3'
def fragment_version = "1.2.5"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
def room_version = "2.2.5"
def archLifecycleVersion = '2.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
// Layouts and design
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'de.hdodenhof:circleimageview:3.0.0'
// font awesome
implementation "com.mikepenz:iconics-core:3.1.0"
implementation 'com.mikepenz:fontawesome-typeface:5.3.1.1@aar'
// http client / REST
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// image downloading and caching library
implementation 'com.squareup.picasso:picasso:2.71828'
// json decoder/encoder
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Torrents and WebRTC
implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
// implementation "com.github.TorrentStream:TorrentStreamServer-Android:1.0.1"
// implementation 'org.webrtc:google-webrtc:1.0.+'
// video player repo:jcenter()
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer"
implementation "com.google.android.exoplayer:exoplayer-hls:$exoplayer"
implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$exoplayer"
implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer"
implementation "com.google.android.exoplayer:extension-okhttp:$exoplayer"
// date formatter
implementation 'org.ocpsoft.prettytime:prettytime:4.0.4.Final'
// Version comparison
implementation 'org.apache.maven:maven-artifact:3.5.0'
// database lib
implementation "androidx.room:room-runtime:$room_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
annotationProcessor "androidx.room:room-compiler:$room_version"
androidTestImplementation "androidx.room:room-testing:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$archLifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$archLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.preference:preference-ktx:1.1.1'
// testing
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "androidx.room:room-testing:$room_version"
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "1.8"
}
}

View File

@ -49,7 +49,7 @@
android:label="@string/title_activity_settings"
android:theme="@style/AppTheme.NoActionBar" /> <!-- Server Selection -->
<activity
android:name=".activity.SelectServerActivity"
android:name=".activity.SearchServerActivity"
android:label="@string/title_activity_select_server"
android:theme="@style/AppTheme.NoActionBar" /> <!-- Me -->
<activity

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
@ -37,6 +36,7 @@ import net.schueller.peertube.R;
import net.schueller.peertube.adapter.ChannelAdapter;
import net.schueller.peertube.adapter.VideoAdapter;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.helper.MetaDataHelper;
import net.schueller.peertube.model.Account;
import net.schueller.peertube.model.Avatar;
@ -96,7 +96,7 @@ public class AccountActivity extends CommonActivity {
apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
userService = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetUserService.class);
userService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(this)).create(GetUserService.class);
recyclerViewVideos = findViewById(R.id.account_video_recyclerView);
recyclerViewChannels = findViewById(R.id.account_channel_recyclerView);
@ -206,7 +206,7 @@ public class AccountActivity extends CommonActivity {
}
} else {
Toast.makeText(AccountActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( AccountActivity.this, null );
}
@ -215,7 +215,7 @@ public class AccountActivity extends CommonActivity {
@Override
public void onFailure(@NonNull Call<Account> call, @NonNull Throwable t) {
Log.wtf(TAG, t.fillInStackTrace());
Toast.makeText(AccountActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( AccountActivity.this, t );
}
});
@ -226,7 +226,7 @@ public class AccountActivity extends CommonActivity {
isLoadingVideos = false;
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(this)).create(GetVideoDataService.class);
Call<VideoList> call;
call = service.getAccountVideosData(displayNameAndHost, videosStart, videosCount, videosSort);
@ -247,8 +247,7 @@ public class AccountActivity extends CommonActivity {
}
} else{
Toast.makeText(AccountActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( AccountActivity.this, null );
}
isLoadingVideos = false;
@ -258,7 +257,7 @@ public class AccountActivity extends CommonActivity {
@Override
public void onFailure(@NonNull Call<VideoList> call, @NonNull Throwable t) {
Log.wtf("err", t.fillInStackTrace());
Toast.makeText(AccountActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( AccountActivity.this, t );
isLoadingVideos = false;
swipeRefreshLayoutVideos.setRefreshing(false);
}
@ -281,7 +280,7 @@ public class AccountActivity extends CommonActivity {
} else {
Toast.makeText(AccountActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( AccountActivity.this, null );
}
@ -290,7 +289,7 @@ public class AccountActivity extends CommonActivity {
@Override
public void onFailure(@NonNull Call<ChannelList> call, @NonNull Throwable t) {
Log.wtf(TAG, t.fillInStackTrace());
Toast.makeText(AccountActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( AccountActivity.this, t );
}
});
}

View File

@ -1,145 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import androidx.annotation.LayoutRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatDelegate;
import android.preference.PreferenceManager;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import static net.schueller.peertube.helper.Constants.DEFAULT_THEME;
import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
// TODO: cleanup this duplication
// Set Night Mode
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
AppCompatDelegate.setDefaultNightMode(sharedPref.getBoolean("pref_dark_mode", false) ?
AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
// Set theme
setTheme(getResources().getIdentifier(
sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME),
"style",
getPackageName())
);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
// public void setSupportActionBar(@Nullable Toolbar toolbar) {
// getDelegate().setSupportActionBar(toolbar);
// }
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@ -1,35 +1,34 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import net.schueller.peertube.R;
import java.util.Locale;
import static net.schueller.peertube.helper.Constants.DEFAULT_THEME;
import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
public class CommonActivity extends AppCompatActivity {
@ -39,30 +38,45 @@ public class CommonActivity extends AppCompatActivity {
// Set Night Mode
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
AppCompatDelegate.setDefaultNightMode(sharedPref.getBoolean("pref_dark_mode", false) ?
AppCompatDelegate.setDefaultNightMode(sharedPref.getBoolean(getString(R.string.pref_dark_mode_key), false) ?
AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
// Set theme
setTheme(getResources().getIdentifier(
sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME),
sharedPref.getString(
getString(R.string.pref_theme_key),
getString(R.string.app_default_theme)
),
"style",
getPackageName())
);
// Set language
String countryCode=sharedPref.getString("pref_language_app","en");
Locale locale=new Locale(countryCode);;
String countryCode = sharedPref.getString(getString(R.string.pref_language_app_key), null);
if (countryCode == null) {
return;
}
setLocale(countryCode);
}
public void setLocale(String languageCode) {
Locale locale = new Locale(languageCode);
//Neither Chinese language choice was working, found this fix on stack overflow
if(countryCode.equals("zh-rCN"))
if (languageCode.equals("zh-rCN"))
locale = Locale.SIMPLIFIED_CHINESE;
if(countryCode.equals("zh-rTW"))
if (languageCode.equals("zh-rTW"))
locale = Locale.TRADITIONAL_CHINESE;
Locale.setDefault(locale);
Configuration config = getBaseContext().getResources().getConfiguration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
}
Resources resources = getResources();
Configuration config = resources.getConfiguration();
config.setLocale(locale);
resources.updateConfiguration(config, resources.getDisplayMetrics());
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
@ -31,6 +30,7 @@ import android.widget.TextView;
import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.model.Avatar;
import net.schueller.peertube.model.Me;
import net.schueller.peertube.network.GetUserService;
@ -118,7 +118,7 @@ public class MeActivity extends CommonActivity {
String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
String baseURL = APIUrlHelper.getUrl(this);
GetUserService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetUserService.class);
GetUserService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(this)).create(GetUserService.class);
Call<Me> call = service.getMe();
@ -162,6 +162,7 @@ public class MeActivity extends CommonActivity {
@Override
public void onFailure(@NonNull Call<Me> call, @NonNull Throwable t) {
ErrorHelper.showToastFromCommunicationError( MeActivity.this, t );
account.setVisibility(View.GONE);
}
});

View File

@ -1,24 +1,22 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -27,40 +25,37 @@ import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.util.Patterns;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import net.schueller.peertube.R;
import net.schueller.peertube.adapter.ServerAdapter;
import net.schueller.peertube.adapter.VideoAdapter;
import net.schueller.peertube.adapter.ServerSearchAdapter;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.model.ServerList;
import net.schueller.peertube.model.VideoList;
import net.schueller.peertube.network.GetServerListDataService;
import net.schueller.peertube.network.GetVideoDataService;
import net.schueller.peertube.network.RetrofitInstance;
import java.util.ArrayList;
import java.util.Objects;
import static net.schueller.peertube.helper.Constants.DEFAULT_THEME;
import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY;
public class SearchServerActivity extends CommonActivity {
public class SelectServerActivity extends CommonActivity {
private ServerAdapter serverAdapter;
private ServerSearchAdapter serverAdapter;
private SwipeRefreshLayout swipeRefreshLayout;
private EditText searchTextView;
private final static String TAG = "SearchServerActivity";
private int currentStart = 0;
private int count = 12;
private final int count = 12;
private String lastSearchtext = "";
private TextView emptyView;
private RecyclerView recyclerView;
@ -76,7 +71,7 @@ public class SelectServerActivity extends CommonActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server_selection);
setContentView(R.layout.activity_search_server);
// Attaching the layout to the toolbar object
Toolbar toolbar = findViewById(R.id.tool_bar_server_selection);
@ -89,21 +84,30 @@ public class SelectServerActivity extends CommonActivity {
}
TextView.OnEditorActionListener onSearchTextValidated = ( textView, i, keyEvent ) -> {
if ( keyEvent != null && keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| i == EditorInfo.IME_ACTION_GO ) {
loadServers(currentStart, count, textView.getText().toString());
}
return false;
};
private void loadList() {
recyclerView = findViewById(R.id.serverRecyclerView);
swipeRefreshLayout = findViewById(R.id.serversSwipeRefreshLayout);
searchTextView = findViewById(R.id.search_server_input_field );
searchTextView.setOnEditorActionListener( onSearchTextValidated );
emptyView = findViewById(R.id.empty_server_selection_view);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(SelectServerActivity.this);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(SearchServerActivity.this);
recyclerView.setLayoutManager(layoutManager);
serverAdapter = new ServerAdapter(new ArrayList<>(), this);
serverAdapter = new ServerSearchAdapter(new ArrayList<>(), this);
recyclerView.setAdapter(serverAdapter);
loadServers(currentStart, count);
loadServers(currentStart, count, searchTextView.getText().toString() );
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
@ -119,7 +123,7 @@ public class SelectServerActivity extends CommonActivity {
if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN)) {
if (!isLoading) {
currentStart = currentStart + count;
loadServers(currentStart, count);
loadServers(currentStart, count, searchTextView.getText().toString());
}
}
}
@ -131,26 +135,29 @@ public class SelectServerActivity extends CommonActivity {
// Refresh items
if (!isLoading) {
currentStart = 0;
loadServers(currentStart, count);
loadServers(currentStart, count, searchTextView.getText().toString());
}
});
}
private void loadServers(int start, int count) {
private void loadServers(int start, int count, String searchtext) {
isLoading = true;
GetServerListDataService service = RetrofitInstance.getRetrofitInstance(
APIUrlHelper.getServerIndexUrl(SelectServerActivity.this)
).create(GetServerListDataService.class);
APIUrlHelper.getServerIndexUrl(SearchServerActivity.this)
, APIUrlHelper.useInsecureConnection(this)).create(GetServerListDataService.class);
if ( !searchtext.equals( lastSearchtext ) )
{
currentStart = 0;
lastSearchtext = searchtext;
}
Call<ServerList> call;
call = service.getInstancesData(start, count);
call = service.getInstancesData(start, count, searchtext);
Log.d("URL Called", call.request().url() + "");
@ -183,7 +190,7 @@ public class SelectServerActivity extends CommonActivity {
@Override
public void onFailure(@NonNull Call<ServerList> call, @NonNull Throwable t) {
Log.wtf("err", t.fillInStackTrace());
Toast.makeText(SelectServerActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( SearchServerActivity.this, t );
isLoading = false;
swipeRefreshLayout.setRefreshing(false);
}

View File

@ -1,191 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
import android.app.AlertDialog;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.widget.EditText;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import net.schueller.peertube.R;
import net.schueller.peertube.adapter.ServerListAdapter;
import net.schueller.peertube.database.Server;
import net.schueller.peertube.database.ServerViewModel;
import net.schueller.peertube.fragment.AddServerFragment;
import java.util.Objects;
public class ServerAddressBookActivity extends CommonActivity implements AddServerFragment.OnFragmentInteractionListener {
private String TAG = "ServerAddressBookActivity";
public static final String EXTRA_REPLY = "net.schueller.peertube.room.REPLY";
private ServerViewModel mServerViewModel;
private AddServerFragment addServerFragment;
private FloatingActionButton floatingActionButton;
private FragmentManager fragmentManager;
@Override
public boolean onSupportNavigateUp() {
finish(); // close this activity as oppose to navigating up
return false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server_address_book);
// Attaching the layout to the toolbar object
Toolbar toolbar = findViewById(R.id.tool_bar_server_address_book);
// Setting toolbar as the ActionBar with setSupportActionBar() call
setSupportActionBar(toolbar);
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_baseline_close_24);
mServerViewModel = new ViewModelProvider(this).get(ServerViewModel.class);
showServers();
floatingActionButton = findViewById(R.id.add_server);
floatingActionButton.setOnClickListener(view -> {
Log.d(TAG, "Click");
fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
addServerFragment = new AddServerFragment();
fragmentTransaction.replace(R.id.server_book, addServerFragment);
fragmentTransaction.commit();
floatingActionButton.hide();
});
}
@Override
public void onFragmentInteraction(Uri uri) {
}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
public void showServers()
{
RecyclerView recyclerView = findViewById(R.id.server_list_recyclerview);
final ServerListAdapter adapter = new ServerListAdapter(this);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// Delete items on swipe
ItemTouchHelper helper = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder,
int direction) {
new AlertDialog.Builder(ServerAddressBookActivity.this)
.setTitle(getString(R.string.server_book_del_alert_title))
.setMessage(getString(R.string.server_book_del_alert_msg))
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
int position = viewHolder.getAdapterPosition();
Server server = adapter.getServerAtPosition(position);
// Toast.makeText(ServerAddressBookActivity.this, "Deleting " +
// server.getServerName(), Toast.LENGTH_LONG).show();
// Delete the server
mServerViewModel.delete(server);
})
.setNegativeButton(android.R.string.no, (dialog, which) -> {
adapter.notifyItemChanged(viewHolder.getAdapterPosition());
})
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
});
helper.attachToRecyclerView(recyclerView);
// Update the cached copy of the words in the adapter.
mServerViewModel.getAllServers().observe(this, adapter::setServers);
}
public void addServer(View view)
{
Log.d(TAG, "addServer");
EditText serverLabel = view.findViewById(R.id.serverLabel);
EditText serverUrl = view.findViewById(R.id.serverUrl);
EditText serverUsername = view.findViewById(R.id.serverUsername);
EditText serverPassword = view.findViewById(R.id.serverPassword);
Server server = new Server(serverLabel.getText().toString());
server.setServerHost(serverUrl.getText().toString());
server.setUsername(serverUsername.getText().toString());
server.setPassword(serverPassword.getText().toString());
mServerViewModel.insert(server);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(addServerFragment);
fragmentTransaction.commit();
floatingActionButton.show();
}
public void testServer()
{
}
}

View File

@ -0,0 +1,168 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity
import android.app.Activity
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels
import androidx.fragment.app.FragmentManager
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import net.schueller.peertube.R
import net.schueller.peertube.adapter.ServerListAdapter
import net.schueller.peertube.database.Server
import net.schueller.peertube.database.ServerViewModel
import net.schueller.peertube.databinding.ActivityServerAddressBookBinding
import net.schueller.peertube.fragment.AddServerFragment
import net.schueller.peertube.helper.APIUrlHelper
import net.schueller.peertube.network.Session
import net.schueller.peertube.service.LoginService
import java.util.*
class ServerAddressBookActivity : CommonActivity() {
private val TAG = "ServerAddressBookActivity"
private val mServerViewModel: ServerViewModel by viewModels()
private var addServerFragment: AddServerFragment? = null
private val fragmentManager: FragmentManager by lazy { supportFragmentManager }
private lateinit var mBinding: ActivityServerAddressBookBinding
override fun onSupportNavigateUp(): Boolean {
finish() // close this activity as oppose to navigating up
return false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityServerAddressBookBinding.inflate(layoutInflater)
setContentView(mBinding.root)
// Setting toolbar as the ActionBar with setSupportActionBar() call
setSupportActionBar(mBinding.toolBarServerAddressBook)
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_baseline_close_24)
}
showServers()
mBinding.addServer.setOnClickListener {
Log.d(TAG, "Click")
val fragmentTransaction = fragmentManager.beginTransaction()
addServerFragment = AddServerFragment().also {
fragmentTransaction.replace(R.id.server_book, it)
fragmentTransaction.commit()
mBinding.addServer.hide()
}
}
}
private fun onServerClick(server: Server) {
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
val editor = sharedPref.edit()
val serverUrl = APIUrlHelper.cleanServerUrl(server.serverHost)
editor.putString(getString(R.string.pref_api_base_key), serverUrl)
editor.apply()
// Logout if logged in
val session = Session.getInstance()
if (session.isLoggedIn) {
session.invalidate()
}
// attempt authentication if we have a username
if (server.username.isNullOrBlank().not()) {
LoginService.Authenticate(server.username, server.password)
}
// close this activity
finish()
Toast.makeText(this, getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show()
}
private fun onEditClick(server: Server) {
val fragmentTransaction = fragmentManager.beginTransaction()
addServerFragment = AddServerFragment.newInstance(server).also {
fragmentTransaction.replace(R.id.server_book, it)
fragmentTransaction.commit()
mBinding.addServer.hide()
}
}
private fun showServers() {
val adapter = ServerListAdapter(mutableListOf(), { onServerClick(it) }, { onEditClick(it) }).also {
mBinding.serverListRecyclerview.adapter = it
}
// Delete items on swipe
val helper = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
AlertDialog.Builder(this@ServerAddressBookActivity)
.setTitle(getString(R.string.server_book_del_alert_title))
.setMessage(getString(R.string.server_book_del_alert_msg))
.setPositiveButton(android.R.string.yes) { _: DialogInterface?, _: Int ->
val position = viewHolder.adapterPosition
val server = adapter.getServerAtPosition(position)
// Toast.makeText(ServerAddressBookActivity.this, "Deleting " +
// server.getServerName(), Toast.LENGTH_LONG).show();
// Delete the server
mServerViewModel.delete(server)
}
.setNegativeButton(android.R.string.no) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.adapterPosition) }
.setIcon(android.R.drawable.ic_dialog_alert)
.show()
}
})
helper.attachToRecyclerView(mBinding.serverListRecyclerview)
// Update the cached copy of the words in the adapter.
mServerViewModel.allServers.observe(this, { servers: List<Server> ->
adapter.setServers(servers)
addServerFragment?.let {
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.remove(it)
fragmentTransaction.commit()
mBinding.addServer.show()
}
})
}
companion object {
const val EXTRA_REPLY = "net.schueller.peertube.room.REPLY"
}
}

View File

@ -1,32 +1,36 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.appcompat.widget.Toolbar;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import net.schueller.peertube.BuildConfig;
import net.schueller.peertube.R;
import java.util.Objects;
public class SettingsActivity extends CommonActivity {
@Override
@ -38,16 +42,72 @@ public class SettingsActivity extends CommonActivity {
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
// Attaching the layout to the toolbar object
Toolbar toolbar = findViewById(R.id.tool_bar_settings);
// Setting toolbar as the ActionBar with setSupportActionBar() call
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_baseline_close_24);
}
}
@Override
public boolean onSupportNavigateUp() {
finish(); // close this activity as oppose to navigating up
return false;
}
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
// write Build Time into pref
Preference pref = findPreference("pref_buildtime");
assert pref != null;
pref.setSummary(Long.toString(BuildConfig.BUILD_TIME));
// double check disabling SSL
final SwitchPreference insecure = (SwitchPreference) findPreference("pref_accept_insecure");
if (insecure != null) {
insecure.setOnPreferenceChangeListener((preference, newValue) -> {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getContext());
SharedPreferences.Editor editor = sharedPref.edit();
boolean currentValue = sharedPref.getBoolean("pref_accept_insecure", false);
if (newValue instanceof Boolean && ((Boolean) newValue) != currentValue) {
final boolean enable = (Boolean) newValue;
Log.v("pref", "enable: " + enable);
Log.v("pref", "currentValue: " + currentValue);
if (enable) {
new Builder(preference.getContext())
.setTitle(R.string.pref_insecure_confirm_title)
.setMessage(R.string.pref_insecure_confirm_message)
.setIcon(R.drawable.ic_info_black_24dp)
.setNegativeButton(R.string.pref_insecure_confirm_no, (dialog, whichButton) -> {
// do nothing
})
.setPositiveButton(R.string.pref_insecure_confirm_yes, (dialog, whichButton) -> {
// OK has been pressed => force the new value and update the checkbox display
editor.putBoolean("pref_accept_insecure", true);
editor.apply();
insecure.setChecked(true);
}).create().show();
// by default ignore the pref change, which can only be validated when OK is pressed
return false;
}
}
return true;
});
}
}
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
@ -50,7 +49,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
@ -60,6 +58,8 @@ import com.mikepenz.iconics.IconicsDrawable;
import net.schueller.peertube.R;
import net.schueller.peertube.adapter.VideoAdapter;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.model.Video;
import net.schueller.peertube.model.VideoList;
import net.schueller.peertube.network.GetUserService;
import net.schueller.peertube.network.GetVideoDataService;
@ -70,6 +70,8 @@ import net.schueller.peertube.service.VideoPlayerService;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import retrofit2.Call;
@ -320,17 +322,24 @@ public class VideoListActivity extends CommonActivity {
isLoading = true;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false";
Set<String> languages = sharedPref.getStringSet("pref_language", null);
String nsfw = sharedPref.getBoolean(getString(R.string.pref_show_nsfw_key), false) ? "both" : "false";
Locale locale = getResources().getConfiguration().locale;
String country = locale.getLanguage();
HashSet<String> countries = new HashSet<>(1);
countries.add(country);
Set<String> languages = sharedPref.getStringSet(getString(R.string.pref_video_language_key), countries);
String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(this)).create(GetVideoDataService.class);
Call<VideoList> call;
if (!searchQuery.equals("")) {
call = service.searchVideosData(start, count, sort, nsfw, searchQuery, filter, languages);
} else if (subscriptions) {
GetUserService userService = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetUserService.class);
GetUserService userService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(this)).create(GetUserService.class);
call = userService.getVideosSubscripions(start, count, sort);
} else {
call = service.getVideosData(start, count, sort, nsfw, filter, languages);
@ -349,7 +358,10 @@ public class VideoListActivity extends CommonActivity {
}
if (response.body() != null) {
videoAdapter.setData(response.body().getVideoArrayList());
ArrayList<Video> videoList = response.body().getVideoArrayList();
if (videoList != null) {
videoAdapter.setData(response.body().getVideoArrayList());
}
}
// no results show no results message
@ -369,7 +381,7 @@ public class VideoListActivity extends CommonActivity {
@Override
public void onFailure(@NonNull Call<VideoList> call, @NonNull Throwable t) {
Log.wtf("err", t.fillInStackTrace());
Toast.makeText(VideoListActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( VideoListActivity.this, t );
isLoading = false;
swipeRefreshLayout.setRefreshing(false);
}
@ -383,7 +395,7 @@ public class VideoListActivity extends CommonActivity {
// only check when we actually need the permission
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED &&
sharedPref.getBoolean("pref_torrent_player", false)) {
sharedPref.getBoolean(getString(R.string.pref_torrent_player_key), false)) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.activity;
@ -36,6 +35,7 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
@ -56,26 +56,25 @@ import net.schueller.peertube.service.VideoPlayerService;
import java.util.ArrayList;
import java.util.Objects;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
//import static net.schueller.peertube.helper.Constants.BACKGROUND_PLAY_PREF_KEY;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PAUSE;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PLAY;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
import static net.schueller.peertube.helper.Constants.BACKGROUND_AUDIO;
import static net.schueller.peertube.helper.Constants.DEFAULT_THEME;
import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY;
import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode;
public class VideoPlayActivity extends AppCompatActivity {
private static final String TAG = "VideoPlayActivity";
private static boolean floatMode = false;
static boolean floatMode = false;
private static final int REQUEST_CODE = 101;
private BroadcastReceiver receiver;
//This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode.
@SuppressLint("NewApi")
public void makePipControls() {
@ -84,7 +83,7 @@ public class VideoPlayActivity extends AppCompatActivity {
ArrayList<RemoteAction> actions = new ArrayList<>();
Intent actionIntent = new Intent(BACKGROUND_AUDIO);
Intent actionIntent = new Intent(getString(R.string.app_background_audio));
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
@SuppressLint({"NewApi", "LocalSuppress"}) Icon icon = Icon.createWithResource(getApplicationContext(), android.R.drawable.stat_sys_speakerphone);
@SuppressLint({"NewApi", "LocalSuppress"}) RemoteAction remoteAction = new RemoteAction(icon, "close pip", "from pip window custom command", pendingIntent);
@ -96,21 +95,21 @@ public class VideoPlayActivity extends AppCompatActivity {
remoteAction = new RemoteAction(icon, "play", "stop the media", pendingIntent);
actions.add(remoteAction);
if (videoPlayerFragment.isPaused()){
Log.e(TAG,"setting actions with play button");
assert videoPlayerFragment != null;
if (videoPlayerFragment.isPaused()) {
Log.e(TAG, "setting actions with play button");
actionIntent = new Intent(ACTION_PLAY);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_play);
remoteAction = new RemoteAction(icon, "play", "play the media", pendingIntent);
actions.add(remoteAction);
} else {
Log.e(TAG,"setting actions with pause button");
Log.e(TAG, "setting actions with pause button");
actionIntent = new Intent(ACTION_PAUSE);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause);
remoteAction = new RemoteAction(icon, "pause", "pause the media", pendingIntent);
actions.add(remoteAction);
}
actions.add(remoteAction);
//add custom actions to pip window
@ -119,12 +118,13 @@ public class VideoPlayActivity extends AppCompatActivity {
.setActions(actions)
.build();
setPictureInPictureParams(params);
}
public void changedToPipMode() {
FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null;
videoPlayerFragment.showControls(false);
//create custom actions
makePipControls();
@ -134,11 +134,12 @@ public class VideoPlayActivity extends AppCompatActivity {
filter.addAction(ACTION_STOP);
filter.addAction(ACTION_PAUSE);
filter.addAction(ACTION_PLAY);
filter.addAction((BACKGROUND_AUDIO));
filter.addAction((getString(R.string.app_background_audio)));
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
assert action != null;
if (action.equals(ACTION_PAUSE)) {
videoPlayerFragment.pauseVideo();
makePipControls();
@ -148,7 +149,7 @@ public class VideoPlayActivity extends AppCompatActivity {
makePipControls();
}
if (action.equals(BACKGROUND_AUDIO)) {
if (action.equals(getString(R.string.app_background_audio))) {
unregisterReceiver(receiver);
finish();
}
@ -161,20 +162,23 @@ public class VideoPlayActivity extends AppCompatActivity {
registerReceiver(receiver, filter);
Log.v(TAG, "switched to pip ");
floatMode=true;
floatMode = true;
videoPlayerFragment.showControls(false);
}
public void changedToNormalMode(){
public void changedToNormalMode() {
FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null;
videoPlayerFragment.showControls(true);
if (receiver != null) {
unregisterReceiver(receiver);
}
Log.v(TAG,"switched to normal");
floatMode=false;
Log.v(TAG, "switched to normal");
floatMode = false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -182,7 +186,10 @@ public class VideoPlayActivity extends AppCompatActivity {
// Set theme
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
setTheme(getResources().getIdentifier(
sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME),
sharedPref.getString(
getString(R.string.pref_theme_key),
getString(R.string.app_default_theme)
),
"style",
getPackageName())
);
@ -197,17 +204,17 @@ public class VideoPlayActivity extends AppCompatActivity {
assert videoPlayerFragment != null;
String playingVideo = videoPlayerFragment.getVideoUuid();
Log.v(TAG, "oncreate click: " + videoUuid +" is trying to replace: "+playingVideo);
Log.v(TAG, "oncreate click: " + videoUuid + " is trying to replace: " + playingVideo);
if (TextUtils.isEmpty(playingVideo)){
Log.v(TAG,"oncreate no video currently playing");
if (TextUtils.isEmpty(playingVideo)) {
Log.v(TAG, "oncreate no video currently playing");
videoPlayerFragment.start(videoUuid);
} else if(!playingVideo.equals(videoUuid)){
Log.v(TAG,"oncreate different video playing currently");
} else if (!playingVideo.equals(videoUuid)) {
Log.v(TAG, "oncreate different video playing currently");
videoPlayerFragment.stopVideo();
videoPlayerFragment.start(videoUuid);
} else {
Log.v(TAG,"oncreate same video playing currently");
Log.v(TAG, "oncreate same video playing currently");
}
// if we are in landscape set the video to fullscreen
@ -225,19 +232,18 @@ public class VideoPlayActivity extends AppCompatActivity {
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null;
String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
Log.v(TAG, "new intent click: " + videoUuid +" is trying to replace: "+videoPlayerFragment.getVideoUuid());
assert videoPlayerFragment != null;
Log.v(TAG, "new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.getVideoUuid());
String playingVideo = videoPlayerFragment.getVideoUuid();
if (TextUtils.isEmpty(playingVideo)){
Log.v(TAG,"new intent no video currently playing");
if (TextUtils.isEmpty(playingVideo)) {
Log.v(TAG, "new intent no video currently playing");
videoPlayerFragment.start(videoUuid);
} else if(!playingVideo.equals(videoUuid)){
Log.v(TAG,"new intent different video playing currently");
} else if (!playingVideo.equals(videoUuid)) {
Log.v(TAG, "new intent different video playing currently");
videoPlayerFragment.stopVideo();
videoPlayerFragment.start(videoUuid);
} else {
Log.v(TAG,"new intent same video playing currently");
Log.v(TAG, "new intent same video playing currently");
}
// if we are in landscape set the video to fullscreen
@ -245,12 +251,10 @@ public class VideoPlayActivity extends AppCompatActivity {
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
setOrientation(true);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
public void onConfigurationChanged(@NonNull Configuration newConfig) {
Log.v(TAG, "onConfigurationChanged()...");
super.onConfigurationChanged(newConfig);
@ -263,58 +267,48 @@ public class VideoPlayActivity extends AppCompatActivity {
}
}
private void setOrientation(Boolean isLandscape) {
FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
if (isLandscape) {
assert videoPlayerFragment != null;
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) Objects.requireNonNull(videoPlayerFragment.getView()).getLayoutParams();
params.width = FrameLayout.LayoutParams.MATCH_PARENT;
params.height = FrameLayout.LayoutParams.MATCH_PARENT;
videoPlayerFragment.getView().setLayoutParams(params);
assert videoPlayerFragment != null;
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) videoPlayerFragment.requireView().getLayoutParams();
params.width = FrameLayout.LayoutParams.MATCH_PARENT;
params.height = isLandscape ? FrameLayout.LayoutParams.MATCH_PARENT : (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250, getResources().getDisplayMetrics());
if (videoMetaFragment != null) {
fragmentManager.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
.hide(videoMetaFragment)
.commit();
videoPlayerFragment.requireView().setLayoutParams(params);
if (videoMetaFragment != null) {
FragmentTransaction transaction = fragmentManager.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
if (isLandscape) {
transaction.hide(videoMetaFragment);
} else {
transaction.show(videoMetaFragment);
}
videoPlayerFragment.setIsFullscreen(true);
} else {
assert videoPlayerFragment != null;
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) Objects.requireNonNull(videoPlayerFragment.getView()).getLayoutParams();
params.width = FrameLayout.LayoutParams.MATCH_PARENT;
params.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250, getResources().getDisplayMetrics());
videoPlayerFragment.getView().setLayoutParams(params);
if (videoMetaFragment != null) {
fragmentManager.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
.show(videoMetaFragment)
.commit();
}
videoPlayerFragment.setIsFullscreen(false);
transaction.commit();
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
videoPlayerFragment.setIsFullscreen(isLandscape);
if ( isLandscape ) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
@Override
protected void onDestroy() {
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null;
videoPlayerFragment.destroyVideo();
super.onDestroy();
Log.v(TAG, "onDestroy...");
}
@ -335,15 +329,6 @@ public class VideoPlayActivity extends AppCompatActivity {
protected void onStop() {
super.onStop();
// SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
//
// Log.v(TAG, "" + sharedPref.getBoolean(BACKGROUND_PLAY_PREF_KEY, false));
//
// if (!sharedPref.getBoolean(BACKGROUND_PLAY_PREF_KEY, false)) {
// Log.v(TAG, "BACKGROUND_PLAY_PREF_KEY...");
// stopService(new Intent(this, VideoPlayerService.class));
// }
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
@ -362,120 +347,155 @@ public class VideoPlayActivity extends AppCompatActivity {
@SuppressLint("NewApi")
@Override
public void onUserLeaveHint () {
public void onUserLeaveHint() {
Log.v(TAG, "onUserLeaveHint()...");
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
String backgroundBehavior = sharedPref.getString("pref_background_behavior","backgroundStop");
VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
switch(backgroundBehavior){
case "backgroundStop":
Log.v(TAG,"stop the video");
videoPlayerFragment.pauseVideo();
stopService(new Intent(this, VideoPlayerService.class));
super.onBackPressed();
break;
case "backgroundAudio":
Log.v(TAG,"play the Audio");
super.onBackPressed();
break;
case "backgroundFloat":
Log.v(TAG,"play in floating video");
//canEnterPIPMode makes sure API level is high enough
if (canEnterPipMode(this)) {
Log.v(TAG, "enabling pip");
enterPipMode();
} else {
Log.v(TAG, "unable to use pip");
}
break;
String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
assert videoPlayerFragment != null;
assert backgroundBehavior != null;
if ( videoMetaDataFragment.isLeaveAppExpected() )
{
super.onUserLeaveHint();
return;
}
Log.v(TAG, "onUserLeaveHint()...");
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
Log.v(TAG, "stop the video");
videoPlayerFragment.pauseVideo();
stopService(new Intent(this, VideoPlayerService.class));
super.onBackPressed();
} else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
Log.v(TAG, "play the Audio");
super.onBackPressed();
} else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
Log.v(TAG, "play in floating video");
//canEnterPIPMode makes sure API level is high enough
if (canEnterPipMode(this)) {
Log.v(TAG, "enabling pip");
enterPipMode();
} else {
Log.v(TAG, "unable to use pip");
}
} else {
// Deal with bad entries from older version
Log.v(TAG, "No setting, fallback");
super.onBackPressed();
}
}
// @RequiresApi(api = Build.VERSION_CODES.O)
// @RequiresApi(api = Build.VERSION_CODES.O)
@SuppressLint("NewApi")
public void onBackPressed() {
Log.v(TAG, "onBackPressed()...");
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
//copying Youtube behavior to have back button exit full screen.
if (videoPlayerFragment.getIsFullscreen()){
Log.v(TAG,"exiting full screen");
assert videoPlayerFragment != null;
// copying Youtube behavior to have back button exit full screen.
if (videoPlayerFragment.getIsFullscreen()) {
Log.v(TAG, "exiting full screen");
videoPlayerFragment.fullScreenToggle();
return;
}
if (sharedPref.getBoolean("pref_back_pause", true)) {
assert videoPlayerFragment != null;
// pause video if pref is enabled
if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) {
videoPlayerFragment.pauseVideo();
}
String backgroundBehavior = sharedPref.getString("pref_background_behavior","backgroundStop");
switch (backgroundBehavior){
case "backgroundStop":
Log.v(TAG,"stop the video");
videoPlayerFragment.pauseVideo();
stopService(new Intent(this, VideoPlayerService.class));
String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
assert backgroundBehavior != null;
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
Log.v(TAG, "stop the video");
videoPlayerFragment.pauseVideo();
stopService(new Intent(this, VideoPlayerService.class));
super.onBackPressed();
} else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
Log.v(TAG, "play the Audio");
super.onBackPressed();
} else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
Log.v(TAG, "play in floating video");
//canEnterPIPMode makes sure API level is high enough
if (canEnterPipMode(this)) {
Log.v(TAG, "enabling pip");
enterPipMode();
//fixes problem where back press doesn't bring up video list after returning from PIP mode
Intent intentSettings = new Intent(this, VideoListActivity.class);
this.startActivity(intentSettings);
} else {
Log.v(TAG, "Unable to enter PIP mode");
super.onBackPressed();
break;
case "backgroundAudio":
Log.v(TAG,"play the Audio");
super.onBackPressed();
break;
case "backgroundFloat":
Log.v(TAG,"play in floating video");
//canEnterPIPMode makes sure API level is high enough
if (canEnterPipMode(this)) {
Log.v(TAG, "enabling pip");
enterPipMode();
//fixes problem where back press doesn't bring up video list after returning from PIP mode
Intent intentSettings = new Intent(this, VideoListActivity.class);
this.startActivity(intentSettings);
} else {
Log.v(TAG,"Unable to enter PIP mode");
super.onBackPressed();
}
break;
}
} else {
// Deal with bad entries from older version
Log.v(TAG, "No setting, fallback");
super.onBackPressed();
}
Log.v(TAG, "onBackPressed()...");
}
public boolean canEnterPipMode(Context context) {
Log.v(TAG,"api version "+Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT<28){
return false;
}
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
return (AppOpsManager.MODE_ALLOWED== appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName()));
}
@RequiresApi(api = Build.VERSION_CODES.O)
public void enterPipMode() {
Rational rational = new Rational(239, 100);
Log.v(TAG,rational.toString());
PictureInPictureParams mParams =
new PictureInPictureParams.Builder()
.setAspectRatio(rational)
// .setSourceRectHint(new Rect(0,500,400,600))
.build();
final FragmentManager fragmentManager = getSupportFragmentManager();
final VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById( R.id.video_player_fragment );
enterPictureInPictureMode(mParams);
if ( videoPlayerFragment.getVideoAspectRatio() == 0 ) {
Log.i( TAG, "impossible to switch to pip" );
} else {
Rational rational = new Rational( (int) ( videoPlayerFragment.getVideoAspectRatio() * 100 ), 100 );
PictureInPictureParams mParams =
new PictureInPictureParams.Builder()
.setAspectRatio( rational )
// .setSourceRectHint(new Rect(0,500,400,600))
.build();
enterPictureInPictureMode( mParams );
}
}
@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
if (isInPictureInPictureMode) {
changedToPipMode();
Log.v(TAG,"switched to pip ");
videoPlayerFragment.useController(false);
if (videoPlayerFragment != null) {
if (isInPictureInPictureMode) {
changedToPipMode();
Log.v(TAG, "switched to pip ");
videoPlayerFragment.useController(false);
} else {
changedToNormalMode();
Log.v(TAG, "switched to normal");
videoPlayerFragment.useController(true);
}
} else {
changedToNormalMode();
Log.v(TAG,"switched to normal");
videoPlayerFragment.useController(true);
Log.e(TAG, "videoPlayerFragment is NULL");
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.adapter;
@ -160,7 +159,7 @@ public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.AccountV
AccountViewHolder(View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
name = itemView.findViewById(R.id.sl_row_name);
thumb = itemView.findViewById(R.id.thumb);
avatar = itemView.findViewById(R.id.avatar);
videoMeta = itemView.findViewById(R.id.videoMeta);

View File

@ -1,158 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.adapter;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.provider.SearchRecentSuggestions;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import net.schueller.peertube.R;
import net.schueller.peertube.activity.SelectServerActivity;
import net.schueller.peertube.activity.ServerAddressBookActivity;
import net.schueller.peertube.activity.VideoListActivity;
import net.schueller.peertube.database.Server;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.provider.SearchSuggestionsProvider;
import net.schueller.peertube.service.LoginService;
import java.util.List;
import static android.app.Activity.RESULT_OK;
public class ServerListAdapter extends RecyclerView.Adapter<ServerListAdapter.ServerViewHolder> {
private final LayoutInflater mInflater;
private List<Server> mServers; // Cached copy of Servers
public ServerListAdapter(Context context) {
this.mInflater = LayoutInflater.from(context);
}
@NonNull
@Override
public ServerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = mInflater.inflate(R.layout.row_serverbook, parent, false);
return new ServerViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ServerViewHolder holder, int position) {
if (mServers != null) {
Server current = mServers.get(position);
holder.serverLabel.setText(current.getServerName());
holder.serverUrl.setText(current.getServerHost());
if (TextUtils.isEmpty(current.getUsername())) {
holder.hasLogin.setVisibility(View.GONE);
} else {
holder.hasLogin.setVisibility(View.VISIBLE);
}
} else {
// Covers the case of data not being ready yet.
holder.serverLabel.setText(R.string.server_book_no_servers_found);
}
holder.itemView.setOnClickListener(v -> {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mInflater.getContext());
SharedPreferences.Editor editor = sharedPref.edit();
String serverUrl = APIUrlHelper.cleanServerUrl(getServerAtPosition(position).getServerHost());
editor.putString("pref_api_base", serverUrl);
editor.apply();
// attempt authentication if we have a username
if (!TextUtils.isEmpty(getServerAtPosition(position).getUsername())) {
LoginService.Authenticate(
getServerAtPosition(position).getUsername(),
getServerAtPosition(position).getPassword()
);
}
// tell server list activity to reload list
Intent intent = new Intent();
((Activity) mInflater.getContext()).setResult(RESULT_OK, intent);
// close this activity
((Activity) mInflater.getContext()).finish();
Toast.makeText(mInflater.getContext(), mInflater.getContext().getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show();
});
//
// holder.itemView.setOnLongClickListener(v -> {
// Log.v("ServerListAdapter", "setOnLongClickListener " + position);
// return true;
// });
}
public void setServers(List<Server> Servers) {
mServers = Servers;
this.notifyDataSetChanged();
}
// getItemCount() is called many times, and when it is first called,
// mServers has not been updated (means initially, it's null, and we can't return null).
@Override
public int getItemCount() {
if (mServers != null)
return mServers.size();
else return 0;
}
static class ServerViewHolder extends RecyclerView.ViewHolder {
TextView serverLabel, serverUrl, serverUsername;
ImageView hasLogin;
private ServerViewHolder(View itemView) {
super(itemView);
serverLabel = itemView.findViewById(R.id.serverLabelRow);
serverUrl = itemView.findViewById(R.id.serverUrlRow);
hasLogin = itemView.findViewById(R.id.sb_row_has_login_icon);
}
}
public Server getServerAtPosition (int position) {
return mServers.get(position);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import net.schueller.peertube.adapter.ServerListAdapter.ServerViewHolder
import net.schueller.peertube.database.Server
import net.schueller.peertube.databinding.RowServerAddressBookBinding
import net.schueller.peertube.utils.visibleIf
class ServerListAdapter(private val mServers: MutableList<Server>, private val onClick: (Server) -> Unit, private val onEditClick: (Server) -> Unit) : RecyclerView.Adapter<ServerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ServerViewHolder {
val binding = RowServerAddressBookBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ServerViewHolder(binding)
}
override fun onBindViewHolder(holder: ServerViewHolder, position: Int) {
holder.bind(mServers[position])
}
fun setServers(servers: List<Server>) {
mServers.clear()
mServers.addAll(servers)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return mServers.size
}
inner class ServerViewHolder (private val binding: RowServerAddressBookBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(server: Server) {
binding.serverLabelRow.text = server.serverName
binding.serverUrlRow.text = server.serverHost
binding.sbRowHasLoginIcon.visibleIf { server.username.isNullOrBlank().not() }
binding.root.setOnClickListener { onClick(server) }
binding.editIcon.setOnClickListener { onEditClick(server) }
}
}
fun getServerAtPosition(position: Int): Server {
return mServers[position]
}
}

View File

@ -1,35 +1,32 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.adapter;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import net.schueller.peertube.R;
import net.schueller.peertube.activity.SelectServerActivity;
import net.schueller.peertube.activity.SearchServerActivity;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.model.Server;
@ -38,17 +35,19 @@ import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import static android.app.Activity.RESULT_OK;
public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.AccountViewHolder> {
public class ServerSearchAdapter extends RecyclerView.Adapter<ServerSearchAdapter.AccountViewHolder> {
private ArrayList<Server> serverList;
private SelectServerActivity activity;
private SearchServerActivity activity;
private String baseUrl;
public ServerAdapter(ArrayList<Server> serverList, SelectServerActivity activity) {
public ServerSearchAdapter(ArrayList<Server> serverList, SearchServerActivity activity) {
this.serverList = serverList;
this.activity = activity;
}
@ -57,7 +56,7 @@ public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.AccountVie
@Override
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.row_server, parent, false);
View view = layoutInflater.inflate(R.layout.row_search_server, parent, false);
baseUrl = APIUrlHelper.getUrl(activity);
@ -74,20 +73,38 @@ public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.AccountVie
R.string.server_selection_signup_allowed_yes :
R.string.server_selection_signup_allowed_no
)));
holder.shortDescription.setText(serverList.get(position).getShortDescription());
holder.videoTotals.setText(
activity.getString(R.string.server_selection_video_totals,
serverList.get(position).getTotalVideos().toString(),
serverList.get(position).getTotalLocalVideos().toString()
));
// don't show description if it hasn't been changes from the default
if (!activity.getString(R.string.peertube_instance_search_default_description).equals(serverList.get(position).getShortDescription())) {
holder.shortDescription.setText(serverList.get(position).getShortDescription());
holder.shortDescription.setVisibility(View.VISIBLE);
} else {
holder.shortDescription.setVisibility(View.GONE);
}
DefaultArtifactVersion serverVersion = new DefaultArtifactVersion(serverList.get(position).getVersion());
// at least version 2.2
DefaultArtifactVersion minVersion22 = new DefaultArtifactVersion("2.2.0");
if (serverVersion.compareTo(minVersion22) >= 0) {
// show NSFW Icon
if (serverList.get(position).getNSFW()) {
holder.isNSFW.setVisibility(View.VISIBLE);
}
}
// select server
holder.itemView.setOnClickListener(v -> {
// SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity);
// SharedPreferences.Editor editor = sharedPref.edit();
String serverUrl = APIUrlHelper.cleanServerUrl(serverList.get(position).getHost());
// editor.putString("pref_api_base", serverUrl);
// editor.apply();
//
//
Toast.makeText(activity, activity.getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show();
Intent intent = new Intent();
@ -138,17 +155,19 @@ public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.AccountVie
return serverList.size();
}
class AccountViewHolder extends RecyclerView.ViewHolder {
static class AccountViewHolder extends RecyclerView.ViewHolder {
TextView name, host, signupAllowed, shortDescription;
TextView name, host, signupAllowed, shortDescription, videoTotals;
ImageView isNSFW;
AccountViewHolder(View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
host = itemView.findViewById(R.id.host);
signupAllowed = itemView.findViewById(R.id.signupAllowed);
shortDescription = itemView.findViewById(R.id.shortDescription);
name = itemView.findViewById(R.id.sl_row_name);
host = itemView.findViewById(R.id.sl_row_host);
signupAllowed = itemView.findViewById(R.id.sl_row_signup_allowed);
shortDescription = itemView.findViewById(R.id.sl_row_short_description);
isNSFW = itemView.findViewById(R.id.sl_row_is_nsfw);
videoTotals = itemView.findViewById(R.id.sl_row_video_totals);
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.adapter;
@ -63,7 +62,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
@Override
public VideoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.row_video, parent, false);
View view = layoutInflater.inflate(R.layout.row_video_list, parent, false);
baseUrl = APIUrlHelper.getUrl(context);
@ -176,7 +175,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
VideoViewHolder(View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
name = itemView.findViewById(R.id.sl_row_name);
thumb = itemView.findViewById(R.id.thumb);
avatar = itemView.findViewById(R.id.avatar);
videoMeta = itemView.findViewById(R.id.videoMeta);

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.application;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database;

View File

@ -1,87 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "server_table")
public class Server {
@PrimaryKey(autoGenerate = true)
private int id;
@NonNull
@ColumnInfo(name = "server_name")
private String serverName;
@ColumnInfo(name = "server_host")
private String serverHost;
@ColumnInfo(name = "username")
private String username;
@ColumnInfo(name = "password")
private String password;
public Server(@NonNull String serverName) {
this.serverName = serverName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getServerHost() {
return serverHost;
}
public void setServerHost(String serverHost) {
this.serverHost = serverHost;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database
import android.os.Parcelable
import androidx.room.PrimaryKey
import androidx.room.ColumnInfo
import androidx.room.Entity
import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(tableName = "server_table")
data class Server(
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
@ColumnInfo(name = "server_name")
var serverName: String,
@ColumnInfo(name = "server_host")
var serverHost: String? = null,
@ColumnInfo(name = "username")
var username: String? = null,
@ColumnInfo(name = "password")
var password: String? = null
) : Parcelable

View File

@ -1,43 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import java.util.List;
@Dao
public interface ServerDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Server server);
@Query("DELETE FROM server_table")
void deleteAll();
@Delete
void delete(Server server);
@Query("SELECT * from server_table ORDER BY server_name DESC")
LiveData<List<Server>> getAllServers();
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
interface ServerDao {
@Insert
suspend fun insert(server: Server)
@Update
suspend fun update(server: Server)
@Query("DELETE FROM server_table")
suspend fun deleteAll()
@Delete
suspend fun delete(server: Server)
@get:Query("SELECT * from server_table ORDER BY server_name DESC")
val allServers: LiveData<List<Server>>
}

View File

@ -1,80 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database;
import android.app.Application;
import android.os.AsyncTask;
import androidx.lifecycle.LiveData;
import java.util.List;
class ServerRepository {
private ServerDao mServerDao;
private LiveData<List<Server>> mAllServers;
ServerRepository(Application application) {
ServerRoomDatabase db = ServerRoomDatabase.getDatabase(application);
mServerDao = db.serverDao();
mAllServers = mServerDao.getAllServers();
}
LiveData<List<Server>> getAllServers() {
return mAllServers;
}
void insert (Server server) {
new insertAsyncTask(mServerDao).execute(server);
}
public void delete(Server server) {
new deleteServerAsyncTask(mServerDao).execute(server);
}
private static class insertAsyncTask extends AsyncTask<Server, Void, Void> {
private ServerDao mAsyncTaskDao;
insertAsyncTask(ServerDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final Server... params) {
mAsyncTaskDao.insert(params[0]);
return null;
}
}
private static class deleteServerAsyncTask extends AsyncTask<Server, Void, Void> {
private ServerDao mAsyncTaskDao;
deleteServerAsyncTask(ServerDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final Server... params) {
mAsyncTaskDao.delete(params[0]);
return null;
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database
import android.app.Application
import android.os.AsyncTask
import androidx.lifecycle.LiveData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class ServerRepository(application: Application) {
private val mServerDao: ServerDao
val allServers: LiveData<List<Server>>
get() = mServerDao.allServers
init {
val db = ServerRoomDatabase.getDatabase(application)
mServerDao = db.serverDao()
}
suspend fun update(server: Server) = withContext(Dispatchers.IO) {
mServerDao.update(server)
}
suspend fun insert(server: Server) = withContext(Dispatchers.IO) {
mServerDao.insert(server)
}
suspend fun delete(server: Server) = withContext(Dispatchers.IO){
mServerDao.delete(server)
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database;

View File

@ -1,45 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database;
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.List;
public class ServerViewModel extends AndroidViewModel {
private ServerRepository mRepository;
private LiveData<List<Server>> mAllServers;
public ServerViewModel (Application application) {
super(application);
mRepository = new ServerRepository(application);
mAllServers = mRepository.getAllServers();
}
public LiveData<List<Server>> getAllServers() { return mAllServers; }
public void insert(Server server) { mRepository.insert(server); }
public void delete(Server server) {mRepository.delete(server);}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.database
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class ServerViewModel(application: Application) : AndroidViewModel(application) {
private val mRepository: ServerRepository = ServerRepository(application)
val allServers: LiveData<List<Server>> = mRepository.allServers
fun insert(server: Server) {
viewModelScope.launch {
mRepository.insert(server)
}
}
fun update(server: Server) {
viewModelScope.launch {
mRepository.update(server)
}
}
fun delete(server: Server) {
viewModelScope.launch {
mRepository.delete(server)
}
}
}

View File

@ -1,178 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.fragment;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.text.TextUtils;
import android.util.Log;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import net.schueller.peertube.R;
import net.schueller.peertube.activity.SelectServerActivity;
import net.schueller.peertube.activity.ServerAddressBookActivity;
import net.schueller.peertube.helper.APIUrlHelper;
import static android.app.Activity.RESULT_OK;
public class AddServerFragment extends Fragment {
public static final String TAG = "AddServerFragment";
public static final Integer PICK_SERVER = 1;
private OnFragmentInteractionListener mListener;
private View mView;
public AddServerFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
// Inflate the layout for this fragment
mView = inflater.inflate(R.layout.fragment_add_server, container, false);
// bind button click
Button addServerButton = mView.findViewById(R.id.addServerButton);
addServerButton.setOnClickListener(view -> {
Activity act = getActivity();
Boolean formValid = true;
// close keyboard
try {
InputMethodManager inputManager = (InputMethodManager)
act.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(act.getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
} catch (Exception e) {
}
EditText selectedLabel = mView.findViewById(R.id.serverLabel);
if ( TextUtils.isEmpty(selectedLabel.getText())){
selectedLabel.setError( act.getString(R.string.server_book_label_is_required ));
Toast.makeText(act, R.string.invalid_url, Toast.LENGTH_LONG).show();
formValid = false;
}
// validate url
EditText selectedUrl = mView.findViewById(R.id.serverUrl);
String serverUrl = APIUrlHelper.cleanServerUrl(selectedUrl.getText().toString());
selectedUrl.setText(serverUrl);
if (!Patterns.WEB_URL.matcher(serverUrl).matches()) {
selectedUrl.setError( act.getString(R.string.server_book_valid_url_is_required ) );
Toast.makeText(act, R.string.invalid_url, Toast.LENGTH_LONG).show();
formValid = false;
}
if (formValid) {
if (act instanceof ServerAddressBookActivity) {
((ServerAddressBookActivity) act).addServer(mView);
}
}
});
// Button testServerButton = mView.findViewById(R.id.testServerButton);
// testServerButton.setOnClickListener(view -> {
// Activity act = getActivity();
// if (act instanceof ServerAddressBookActivity) {
// ((ServerAddressBookActivity) act).testServer();
// }
// });
Button pickServerUrl = mView.findViewById(R.id.pickServerUrl);
pickServerUrl.setOnClickListener(view -> {
Intent intentServer = new Intent(getActivity(), SelectServerActivity.class);
this.startActivityForResult(intentServer, PICK_SERVER);
});
return mView;
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_SERVER) {
if(resultCode == RESULT_OK) {
String serverUrlTest = data.getStringExtra("serverUrl");
//Log.d(TAG, "serverUrl " + serverUrlTest);
EditText serverUrl = mView.findViewById(R.id.serverUrl);
serverUrl.setText(serverUrlTest);
EditText serverLabel = mView.findViewById(R.id.serverLabel);
if ("".equals(serverLabel.getText().toString())) {
serverLabel.setText(data.getStringExtra("serverName"));
}
}
}
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}

View File

@ -0,0 +1,173 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.fragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.util.Patterns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import net.schueller.peertube.R
import net.schueller.peertube.activity.SearchServerActivity
import net.schueller.peertube.database.Server
import net.schueller.peertube.database.ServerViewModel
import net.schueller.peertube.databinding.FragmentAddServerBinding
import net.schueller.peertube.helper.APIUrlHelper
import net.schueller.peertube.utils.hideKeyboard
class AddServerFragment : Fragment() {
private lateinit var mBinding: FragmentAddServerBinding
private val mServerViewModel: ServerViewModel by activityViewModels()
private var mServer: Server? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
mServer = it.getParcelable(SERVER_ARG)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mBinding = FragmentAddServerBinding.inflate(inflater, container, false)
return mBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initServerEdit()
mBinding.addServerButton.setOnClickListener {
var formValid = true
hideKeyboard()
if (mBinding.serverLabel.text.toString().isBlank()) {
mBinding.serverLabel.error = getString(R.string.server_book_label_is_required)
Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_LONG).show()
formValid = false
}
// validate url
mBinding.serverUrl.apply {
APIUrlHelper.cleanServerUrl(text.toString())?.let {
setText(it)
if (!Patterns.WEB_URL.matcher(it).matches()) {
error = getString(R.string.server_book_valid_url_is_required)
Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_LONG).show()
formValid = false
}
}
}
if (formValid) {
mServer?.apply {
mBinding.let {
serverName = it.serverLabel.text.toString()
serverHost = it.serverUrl.text.toString()
username = it.serverUsername.text.toString()
password = it.serverPassword.text.toString()
mServerViewModel.update(this)
}
return@setOnClickListener
}
mBinding.apply {
val server = Server(serverName = serverLabel.text.toString())
server.serverHost = serverUrl.text.toString()
server.username = serverUsername.text.toString()
server.password = serverPassword.text.toString()
mServerViewModel.insert(server)
}
}
}
mBinding.pickServerUrl.setOnClickListener {
val intentServer = Intent(activity, SearchServerActivity::class.java)
this.startActivityForResult(intentServer, PICK_SERVER)
}
}
private fun initServerEdit() {
mServer?.let {
mBinding.apply {
serverLabel.setText(it.serverName)
serverUrl.setText(it.serverHost)
serverUsername.setText(it.username)
serverPassword.setText(it.password)
addServerButton.text = getString(R.string.server_book_add_save_button)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != PICK_SERVER) {
return
}
if (resultCode != Activity.RESULT_OK) {
return
}
val serverUrlTest = data?.getStringExtra("serverUrl")
//Log.d(TAG, "serverUrl " + serverUrlTest);
mBinding.serverUrl.setText(serverUrlTest)
mBinding.serverLabel.apply {
if (text.toString().isBlank()) {
setText(data?.getStringExtra("serverName"))
}
}
}
companion object {
private const val TAG = "AddServerFragment"
private const val PICK_SERVER = 1
private const val SERVER_ARG = "server"
fun newInstance(server: Server) = AddServerFragment().apply {
arguments = Bundle().also {
it.putParcelable(SERVER_ARG, server)
}
}
}
}

View File

@ -1,22 +1,22 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.fragment;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -44,7 +44,7 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
public static final String TAG = "VideoMenuQuality";
private static File autoQualityFile;
public static VideoMenuQualityFragment newInstance(ArrayList<File> files) {
public static VideoMenuQualityFragment newInstance(Context context, ArrayList<File> files) {
mFiles = files;
@ -53,7 +53,7 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
autoQualityFile = new File();
Resolution autoQualityResolution = new Resolution();
autoQualityResolution.setId(0);
autoQualityResolution.setLabel("Auto");
autoQualityResolution.setLabel(context.getString(R.string.menu_video_options_quality_automated));
autoQualityFile.setId(0);
autoQualityFile.setResolution(autoQualityResolution);
}
@ -74,11 +74,11 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
false);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getContext());
Integer videoQuality = sharedPref.getInt("pref_quality", 0);
Integer videoQuality = sharedPref.getInt(getString(R.string.pref_quality_key), 0);
for (File file : mFiles) {
LinearLayout menuRow = (LinearLayout) inflater.inflate(R.layout.row_popup_menu, null);
LinearLayout menuRow = (LinearLayout) inflater.inflate(R.layout.row_popup_menu, container);
TextView iconView = menuRow.findViewById(R.id.video_quality_icon);
iconView.setId(file.getResolution().getId());
@ -90,7 +90,7 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
textView.setOnClickListener(view1 -> {
// Log.v(TAG, file.getResolution().getLabel());
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("pref_quality", file.getResolution().getId());
editor.putInt(getString(R.string.pref_quality_key), file.getResolution().getId());
editor.apply();
for (File fileV : mFiles) {

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.fragment;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.fragment;
@ -39,6 +38,7 @@ import com.squareup.picasso.Picasso;
import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.helper.MetaDataHelper;
import net.schueller.peertube.intents.Intents;
import net.schueller.peertube.model.Account;
@ -65,25 +65,41 @@ public class VideoMetaDataFragment extends Fragment {
private static final String TAG = "VideoMetaDataFragment";
private static final String RATING_NONE = "none";
private static final String RATING_LIKE = "like";
private static final String RATING_DISLIKE = "dislike";
private Rating videoRating;
private ColorStateList defaultTextColor;
private boolean leaveAppExpected = false;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_video_meta, container, false);
}
public void updateVideoMeta(Video video, VideoPlayerService mService) {
@Override
public void onPause()
{
leaveAppExpected = false;
super.onPause();
}
public boolean isLeaveAppExpected()
{
return leaveAppExpected;
}
public void updateVideoMeta(Video video, VideoPlayerService mService) {
Context context = getContext();
Activity activity = getActivity();
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class);
// Thumbs up
Button thumbsUpButton = activity.findViewById(R.id.video_thumbs_up);
@ -91,26 +107,23 @@ public class VideoMetaDataFragment extends Fragment {
thumbsUpButton.setText(R.string.video_thumbs_up_icon);
new Iconics.IconicsBuilder().ctx(context).on(thumbsUpButton).build();
thumbsUpButton.setOnClickListener(v -> {
rateVideo(true, video.getId());
rateVideo(true, video);
});
TextView thumbsUpButtonTotal = activity.findViewById(R.id.video_thumbs_up_total);
thumbsUpButtonTotal.setText(video.getLikes().toString());
// Thumbs Down
Button thumbsDownButton = activity.findViewById(R.id.video_thumbs_down);
thumbsDownButton.setText(R.string.video_thumbs_down_icon);
new Iconics.IconicsBuilder().ctx(context).on(thumbsDownButton).build();
thumbsDownButton.setOnClickListener(v -> {
rateVideo(false, video.getId());
rateVideo(false, video);
});
// video rating
videoRating = new Rating();
videoRating.setRating("none"); // default
updateVideoRating();
videoRating.setRating(RATING_NONE); // default
updateVideoRating(video);
// Retrieve which rating the user gave to this video
if (Session.getInstance().isLoggedIn()) {
Call<Rating> call = videoDataService.getVideoRating(video.getId());
call.enqueue(new Callback<Rating>() {
@ -118,25 +131,26 @@ public class VideoMetaDataFragment extends Fragment {
@Override
public void onResponse(Call<Rating> call, Response<Rating> response) {
videoRating = response.body();
updateVideoRating();
updateVideoRating(video);
}
@Override
public void onFailure(Call<Rating> call, Throwable t) {
// Toast.makeText(context, "Rating Failed", Toast.LENGTH_SHORT).show();
ErrorHelper.showToastFromCommunicationError( getActivity(), t );
// Do nothing.
}
});
}
TextView thumbsDownButtonTotal = activity.findViewById(R.id.video_thumbs_down_total);
thumbsDownButtonTotal.setText(video.getDislikes().toString());
// Share
Button videoShareButton = activity.findViewById(R.id.video_share);
videoShareButton.setText(R.string.video_share_icon);
new Iconics.IconicsBuilder().ctx(context).on(videoShareButton).build();
videoShareButton.setOnClickListener(v -> Intents.Share(context, video));
videoShareButton.setOnClickListener(v ->
{
leaveAppExpected = true;
Intents.Share( context, video );
} );
// Download
Button videoDownloadButton = activity.findViewById(R.id.video_download);
@ -145,6 +159,7 @@ public class VideoMetaDataFragment extends Fragment {
videoDownloadButton.setOnClickListener(v -> {
// get permission to store file
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
leaveAppExpected = true;
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Intents.Download(context, video);
@ -156,7 +171,6 @@ public class VideoMetaDataFragment extends Fragment {
}
});
Account account = video.getAccount();
// owner / creator Avatar
@ -172,7 +186,7 @@ public class VideoMetaDataFragment extends Fragment {
// title / name
TextView videoName = activity.findViewById(R.id.name);
TextView videoName = activity.findViewById(R.id.sl_row_name);
videoName.setText(video.getName());
// created at / views
@ -198,7 +212,6 @@ public class VideoMetaDataFragment extends Fragment {
TextView videoDescription = activity.findViewById(R.id.description);
videoDescription.setText(video.getDescription());
// video privacy
TextView videoPrivacy = activity.findViewById(R.id.video_privacy);
videoPrivacy.setText(video.getPrivacy().getLabel());
@ -211,7 +224,7 @@ public class VideoMetaDataFragment extends Fragment {
TextView videoLicense = activity.findViewById(R.id.video_license);
videoLicense.setText(video.getLicence().getLabel());
// video langauge
// video language
TextView videoLanguage = activity.findViewById(R.id.video_language);
videoLanguage.setText(video.getLanguage().getLabel());
@ -219,7 +232,6 @@ public class VideoMetaDataFragment extends Fragment {
TextView videoTags = activity.findViewById(R.id.video_tags);
videoTags.setText(android.text.TextUtils.join(", ", video.getTags()));
// more button
TextView moreButton = activity.findViewById(R.id.moreButton);
moreButton.setText(R.string.video_more_icon);
@ -259,8 +271,7 @@ public class VideoMetaDataFragment extends Fragment {
}
void updateVideoRating() {
void updateVideoRating(Video video) {
Button thumbsUpButton = getActivity().findViewById(R.id.video_thumbs_up);
Button thumbsDownButton = getActivity().findViewById(R.id.video_thumbs_down);
@ -269,68 +280,94 @@ public class VideoMetaDataFragment extends Fragment {
TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorPrimary});
int accentColor = a.getColor(0, 0);
if (videoRating.getRating().equals(getString(R.string.video_rating_none))) {
thumbsUpButton.setTextColor(defaultTextColor);
thumbsDownButton.setTextColor(defaultTextColor);
//Log.v(TAG, getString(R.string.video_rating_none));
} else if (videoRating.getRating().equals(getString(R.string.video_rating_like))) {
thumbsUpButton.setTextColor(accentColor);
thumbsDownButton.setTextColor(defaultTextColor);
//Log.v(TAG, getString(R.string.video_rating_like));
} else if (videoRating.getRating().equals(getString(R.string.video_rating_dislike))) {
thumbsUpButton.setTextColor(defaultTextColor);
thumbsDownButton.setTextColor(accentColor);
//Log.v(TAG, getString(R.string.video_rating_dislike));
// Change the color of the thumbs
switch (videoRating.getRating()) {
case RATING_NONE:
thumbsUpButton.setTextColor(defaultTextColor);
thumbsDownButton.setTextColor(defaultTextColor);
break;
case RATING_LIKE:
thumbsUpButton.setTextColor(accentColor);
thumbsDownButton.setTextColor(defaultTextColor);
break;
case RATING_DISLIKE:
thumbsUpButton.setTextColor(defaultTextColor);
thumbsDownButton.setTextColor(accentColor);
break;
}
// Update the texts
TextView thumbsDownTotal = getActivity().findViewById(R.id.video_thumbs_down_total);
TextView thumbsUpTotal = getActivity().findViewById(R.id.video_thumbs_up_total);
thumbsUpTotal.setText(String.valueOf(video.getLikes()));
thumbsDownTotal.setText(String.valueOf(video.getDislikes()));
a.recycle();
}
void rateVideo(Boolean rate, Integer videoId) {
// TODO cleanup
void rateVideo(Boolean like, Video video) {
if (Session.getInstance().isLoggedIn()) {
final String ratePayload;
String ratePayload = getString(R.string.video_rating_none);
if (rate) {
// thumbsup
if (videoRating.getRating().equals(getString(R.string.video_rating_none))) {
ratePayload = getString(R.string.video_rating_like);
}
} else {
// thumbsdown
if (videoRating.getRating().equals(getString(R.string.video_rating_none))) {
ratePayload = getString(R.string.video_rating_dislike);
}
switch (videoRating.getRating()) {
case RATING_LIKE:
ratePayload = like ? RATING_NONE : RATING_DISLIKE;
break;
case RATING_DISLIKE:
ratePayload = like ? RATING_LIKE : RATING_NONE;
break;
case RATING_NONE:
default:
ratePayload = like ? RATING_LIKE : RATING_DISLIKE;
break;
}
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"), "{\"rating\":\"" + ratePayload + "\"}");
String apiBaseURL = APIUrlHelper.getUrlWithVersion(getContext());
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(getContext())).create(GetVideoDataService.class);
Call<ResponseBody> call = videoDataService.rateVideo(videoId, body);
final String newRating = ratePayload;
Call<ResponseBody> call = videoDataService.rateVideo(video.getId(), body);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//Log.v(TAG, response.toString());
// if 20x update likes
// if 20x, update likes/dislikes
if (response.isSuccessful()) {
videoRating.setRating(newRating);
updateVideoRating();
String previousRating = videoRating.getRating();
// TODO: update count under thumb
// Update the likes/dislikes count of the video, if needed.
// This is only a visual trick, as the actual like/dislike count has
// already been modified on the PeerTube instance.
if (!previousRating.equals(ratePayload)) {
switch (previousRating) {
case RATING_NONE:
if (ratePayload.equals(RATING_LIKE)) {
video.setLikes(video.getLikes() + 1);
} else {
video.setDislikes(video.getDislikes() + 1);
}
break;
case RATING_LIKE:
video.setLikes(video.getLikes() - 1);
if (ratePayload.equals(RATING_DISLIKE)) {
video.setDislikes(video.getDislikes() + 1);
}
break;
case RATING_DISLIKE:
video.setDislikes(video.getDislikes() - 1);
if (ratePayload.equals(RATING_LIKE)) {
video.setLikes(video.getLikes() + 1);
}
break;
}
}
videoRating.setRating(ratePayload);
updateVideoRating(video);
}
}
@ -341,9 +378,7 @@ public class VideoMetaDataFragment extends Fragment {
});
} else {
Toast.makeText(getContext(), getString(R.string.video_login_required_for_service), Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -1,25 +1,24 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.fragment;
import android.annotation.SuppressLint;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -28,6 +27,7 @@ import android.widget.TextView;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.mikepenz.iconics.Iconics;
import net.schueller.peertube.R;
import net.schueller.peertube.model.File;
import net.schueller.peertube.service.VideoPlayerService;
@ -35,6 +35,7 @@ import net.schueller.peertube.service.VideoPlayerService;
import java.util.ArrayList;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
public class VideoOptionsFragment extends BottomSheetDialogFragment {
@ -62,32 +63,41 @@ public class VideoOptionsFragment extends BottomSheetDialogFragment {
LinearLayout menuHolder = view.findViewById(R.id.video_options_popup);
// Video Speed
LinearLayout menuRow = (LinearLayout) inflater.inflate(R.layout.row_popup_menu, null);
LinearLayout menuRow = (LinearLayout) inflater.inflate(R.layout.row_popup_menu, container);
TextView iconView = menuRow.findViewById(R.id.video_quality_icon);
TextView textView = menuRow.findViewById(R.id.video_quality_text);
textView.setText(getString(R.string.menu_video_options_playback_speed));
textView.setText(
getString(
R.string.menu_video_options_playback_speed,
getCurrentVideoPlaybackSpeedString(videoPlayerService.getPlayBackSpeed()
)
)
);
iconView.setText(R.string.video_option_speed_icon);
new Iconics.IconicsBuilder().ctx(getContext()).on(iconView).build();
textView.setOnClickListener(view1 -> {
VideoMenuSpeedFragment videoMenuSpeedFragment =
VideoMenuSpeedFragment.newInstance(videoPlayerService);
videoMenuSpeedFragment.show(getActivity().getSupportFragmentManager(),
videoMenuSpeedFragment.show(requireActivity().getSupportFragmentManager(),
VideoMenuSpeedFragment.TAG);
});
menuHolder.addView(menuRow);
// Video Quality
LinearLayout menuRow2 = (LinearLayout) inflater.inflate(R.layout.row_popup_menu, null);
LinearLayout menuRow2 = (LinearLayout) inflater.inflate(R.layout.row_popup_menu, container);
TextView iconView2 = menuRow2.findViewById(R.id.video_quality_icon);
TextView textView2 = menuRow2.findViewById(R.id.video_quality_text);
textView2.setText(getString(R.string.menu_video_options_quality));
textView2.setText(String.format(getString(R.string.menu_video_options_quality), getCurrentVideoQuality(files)));
iconView2.setText(R.string.video_option_quality_icon);
new Iconics.IconicsBuilder().ctx(getContext()).on(iconView2).build();
textView2.setOnClickListener(view1 -> {
VideoMenuQualityFragment videoMenuQualityFragment =
VideoMenuQualityFragment.newInstance(files);
videoMenuQualityFragment.show(getActivity().getSupportFragmentManager(),
videoMenuQualityFragment.TAG);
VideoMenuQualityFragment.newInstance(getContext(), files);
videoMenuQualityFragment.show(requireActivity().getSupportFragmentManager(),
VideoMenuQualityFragment.TAG);
});
menuHolder.addView(menuRow2);
@ -95,4 +105,26 @@ public class VideoOptionsFragment extends BottomSheetDialogFragment {
}
private String getCurrentVideoQuality(ArrayList<File> files) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getContext());
Integer videoQuality = sharedPref.getInt(getString(R.string.pref_quality_key), 0);
for (File file : files) {
if (videoQuality.equals(file.getResolution().getId())) {
return file.getResolution().getLabel();
}
}
// Returning Automated as a placeholder
return getString(R.string.menu_video_options_quality_automated);
}
private String getCurrentVideoPlaybackSpeedString(float playbackSpeed) {
String speed = String.valueOf(playbackSpeed);
// Remove all non-digit characters from the string
speed = speed.replaceAll("[^0-9]", "");
// Dynamically get the localized string corresponding to the speed
@StringRes int stringId = getResources().getIdentifier("video_speed_" + speed, "string", videoPlayerService.getPackageName());
return getString(stringId);
}
}

View File

@ -1,24 +1,22 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.fragment;
import android.app.Activity;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@ -60,21 +58,23 @@ import com.mikepenz.iconics.Iconics;
import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.model.File;
import net.schueller.peertube.model.Video;
import net.schueller.peertube.network.GetVideoDataService;
import net.schueller.peertube.network.RetrofitInstance;
import net.schueller.peertube.service.VideoPlayerService;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode;
public class VideoPlayerFragment extends Fragment implements VideoRendererEventListener {
private String mVideoUuid;
@ -86,6 +86,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
private VideoPlayerService mService;
private TorrentStream torrentStream;
private LinearLayout torrentStatus;
private float aspectRatio;
private static final String TAG = "VideoPlayerFragment";
private GestureDetector mDetector;
@ -112,6 +113,14 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
mBound = false;
}
};
private AspectRatioFrameLayout.AspectRatioListener aspectRatioListerner = new AspectRatioFrameLayout.AspectRatioListener()
{
@Override
public void onAspectRatioUpdated( float targetAspectRatio, float naturalAspectRatio, boolean aspectRatioMismatch )
{
aspectRatio = targetAspectRatio;
}
};
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
@ -134,6 +143,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
progressBar = activity.findViewById(R.id.torrent_progress);
progressBar.setMax(100);
assert context != null;
simpleExoPlayerView = new PlayerView(context);
simpleExoPlayerView = activity.findViewById(R.id.video_view);
@ -143,6 +153,8 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
mDetector = new GestureDetector(context, new MyGestureListener());
simpleExoPlayerView.setOnTouchListener(touchListener);
simpleExoPlayerView.setAspectRatioListener( aspectRatioListerner );
torrentStatus = activity.findViewById(R.id.exo_torrent_status);
// Full screen Icon
@ -171,7 +183,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
// get video details from api
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class);
Call<Video> call = service.getVideoData(mVideoUuid);
@ -195,30 +207,31 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
@Override
public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) {
Log.wtf(TAG, t.fillInStackTrace());
Toast.makeText(context, "Something went wrong: "+t.getLocalizedMessage(), Toast.LENGTH_LONG).show();
ErrorHelper.showToastFromCommunicationError( getActivity(), t );
}
});
}
public void useController(boolean value){
if (mBound){
public void useController(boolean value) {
if (mBound) {
simpleExoPlayerView.setUseController(value);
}
}
private void playVideo(Video video) {
Context context = getContext();
// video Meta fragment
assert getFragmentManager() != null;
VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment)
getFragmentManager().findFragmentById(R.id.video_meta_data_fragment);
requireActivity().getSupportFragmentManager().findFragmentById(R.id.video_meta_data_fragment);
assert videoMetaDataFragment != null;
videoMetaDataFragment.updateVideoMeta(video, mService);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPref.getBoolean("pref_torrent_player", false)) {
if (sharedPref.getBoolean(getString(R.string.pref_torrent_player_key), false)) {
torrentStatus.setVisibility(View.VISIBLE);
String stream = video.getFiles().get(0).getTorrentUrl();
Log.v(TAG, "getTorrentUrl : " + video.getFiles().get(0).getTorrentUrl());
@ -226,20 +239,25 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
torrentStream.startStream(stream);
} else {
Integer videoQuality = sharedPref.getInt("pref_quality", 0);
Integer videoQuality = sharedPref.getInt(getString(R.string.pref_quality_key), 0);
//get video qualities
String urlToPlay = video.getFiles().get(0).getFileUrl();
for (File file :video.getFiles()) {
// Set quality if it matches
if (file.getResolution().getId().equals(videoQuality)) {
urlToPlay = file.getFileUrl();
/// #
if (video.getFiles().size() > 0) {
String urlToPlay = video.getFiles().get( 0 ).getFileUrl();
for ( File file : video.getFiles() ) {
// Set quality if it matches
if ( file.getResolution().getId().equals( videoQuality ) ) {
urlToPlay = file.getFileUrl();
}
}
mService.setCurrentStreamUrl( urlToPlay );
torrentStatus.setVisibility(View.GONE);
startPlayer();
} else {
stopVideo();
Toast.makeText(context, R.string.api_error, Toast.LENGTH_LONG).show();
}
mService.setCurrentStreamUrl(urlToPlay);
torrentStatus.setVisibility(View.GONE);
startPlayer();
}
Log.v(TAG, "end of load Video");
@ -247,7 +265,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
}
private void startPlayer() {
Util.startForegroundService(Objects.requireNonNull(getContext()), videoPlayerIntent);
Util.startForegroundService(requireContext(), videoPlayerIntent);
}
@ -259,30 +277,37 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
}
public void pauseVideo() {
if (mBound){
if (mBound) {
mService.player.setPlayWhenReady(false);
}
}
public void pauseToggle() {
if (mBound) {
mService.player.setPlayWhenReady(!mService.player.getPlayWhenReady());
}
}
public void unPauseVideo() {
if (mBound) {
mService.player.setPlayWhenReady(true);
}
}
public boolean isPaused(){
public float getVideoAspectRatio() { return aspectRatio; }
public boolean isPaused() {
return !mService.player.getPlayWhenReady();
}
public void showControls(boolean value){
public void showControls(boolean value) {
simpleExoPlayerView.setUseController(value);
}
public void stopVideo() {
if (mBound) {
Objects.requireNonNull(getContext()).unbindService(mConnection);
requireContext().unbindService(mConnection);
mBound = false;
}
}
@ -290,7 +315,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
public void setIsFullscreen(Boolean fullscreen) {
isFullscreen = fullscreen;
TextView fullscreenButton = getActivity().findViewById(R.id.exo_fullscreen);
TextView fullscreenButton = requireActivity().findViewById(R.id.exo_fullscreen);
if (fullscreen) {
fullscreenButton.setText(R.string.video_compress_icon);
} else {
@ -302,15 +327,17 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
public Boolean getIsFullscreen() {
return isFullscreen;
}
public void fullScreenToggle() {
if (!isFullscreen) {
setIsFullscreen(true);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setIsFullscreen(false);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
/**
* Torrent Playback
*
@ -404,18 +431,6 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
Log.v(TAG, "onVideoDisabled()...");
}
public static boolean canEnterPipMode(Context context) {
Log.v(TAG,"api version "+Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT<28){
return false;
}
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
if (!"BackgroundFloat".equals(sharedPref.getString("pref_background_behavior","backgroundStop"))){
return false;
}
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
return (AppOpsManager.MODE_ALLOWED== appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName()));
}
View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@ -423,62 +438,64 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
}
};
public String getVideoUuid(){
public String getVideoUuid() {
return mVideoUuid;
}
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
/*
@Override
public boolean onDown(MotionEvent event) {
Log.d("TAG","onDown: ");
return true;
}
/*
@Override
public boolean onDown(MotionEvent event) {
Log.d("TAG","onDown: ");
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.i("TAG", "onSingleTapConfirmed: ");
pauseToggle();
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.i("TAG", "onSingleTapConfirmed: ");
pauseToggle();
return true;
}
@Override
public void onLongPress(MotionEvent e) {
Log.i("TAG", "onLongPress: ");
}
@Override
public void onLongPress(MotionEvent e) {
Log.i("TAG", "onLongPress: ");
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.i("TAG", "onDoubleTap: ");
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.i("TAG", "onDoubleTap: ");
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.i("TAG", "onScroll: ");
return true;
}
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.i("TAG", "onScroll: ");
return true;
}
*/
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(TAG ,event1.toString());
Log.d(TAG,event2.toString());
Log.d(TAG, event1.toString());
Log.d(TAG, event2.toString());
Log.d(TAG, String.valueOf(velocityX));
Log.d(TAG , String.valueOf(velocityY));
Log.d(TAG, String.valueOf(velocityY));
//arbitrarily velocity speeds that seem to work to differentiate events.
if (velocityY>4000){
Log.d(TAG,"we have a drag down event");
if (velocityY > 4000) {
Log.d(TAG, "we have a drag down event");
if (canEnterPipMode(getContext())) {
getActivity().enterPictureInPictureMode();
requireActivity().enterPictureInPictureMode();
}
}
if ((velocityX>2000) && (Math.abs(velocityY) <2000)){
Log.d(TAG,"swipe right "+velocityY);
if ((velocityX > 2000) && (Math.abs(velocityY) < 2000)) {
Log.d(TAG, "swipe right " + velocityY);
}
if ((velocityX<2000) && (Math.abs(velocityY)<2000)){
Log.d(TAG,"swipe left "+velocityY);
if ((velocityX < 2000) && (Math.abs(velocityY) < 2000)) {
Log.d(TAG, "swipe left " + velocityY);
}
return true;
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.helper;
@ -32,7 +31,7 @@ public class APIUrlHelper{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
// validate URL is valid
String URL = sharedPref.getString("pref_api_base", context.getResources().getString(R.string.pref_default_api_base_url));
String URL = sharedPref.getString(context.getString(R.string.pref_api_base_key), context.getResources().getString(R.string.pref_default_api_base_url));
if (!URLUtil.isValidUrl(URL)) {
return "http://invalid";
}
@ -43,6 +42,11 @@ public class APIUrlHelper{
return APIUrlHelper.getUrl(context) + "/api/v1/";
}
public static Boolean useInsecureConnection(Context context) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
return sharedPref.getBoolean(context.getString(R.string.pref_accept_insecure), false);
}
public static String getShareUrl(Context context, String videoUuid) {
return APIUrlHelper.getUrl(context) + "/videos/watch/" + videoUuid;
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.helper;
public class Constants {
public static final String THEME_PREF_KEY = "pref_theme";
public static final String DEFAULT_THEME = "AppTheme.BLUE";
public static final String BACKGROUND_PLAY_PREF_KEY = "pref_background_play";
public static final String BACKGROUND_AUDIO = "BACKGROUND_AUDIO";
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.helper;
import android.content.Context;
import android.widget.Toast;
import net.schueller.peertube.R;
import java.io.IOException;
import retrofit2.HttpException;
public class ErrorHelper {
public static void showToastFromCommunicationError( Context context, Throwable throwable ) {
if (throwable instanceof IOException ) {
//handle network error
Toast.makeText( context, context.getString( R.string.network_error), Toast.LENGTH_SHORT).show();
} else if (throwable instanceof HttpException ) {
//handle HTTP error response code
Toast.makeText(context, context.getString(R.string.api_error), Toast.LENGTH_SHORT).show();
} else {
//handle other exceptions
Toast.makeText(context, context.getString(R.string.api_error), Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -1,34 +1,44 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.helper;
import android.content.Context;
import android.text.format.DateUtils;
import net.schueller.peertube.R;
import java.time.Duration;
import java.time.Period;
import org.ocpsoft.prettytime.PrettyTime;
import java.util.Date;
import java.util.Locale;
public class MetaDataHelper {
public static String getMetaString(Date getCreatedAt, Integer viewCount, Context context) {
return (DateUtils.getRelativeTimeSpanString(context,getCreatedAt.getTime(),false).toString() +
// Compatible with SDK 21+
String currentLanguage = Locale.getDefault().getDisplayLanguage();
PrettyTime p = new PrettyTime(currentLanguage);
String relativeTime = p.format(new Date(getCreatedAt.getTime()));
return (relativeTime +
context.getResources().getString(R.string.meta_data_seperator) +
viewCount + context.getResources().getString(R.string.meta_data_views));
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.helper;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
import net.schueller.peertube.R;
public class VideoHelper {
private static final String TAG = "VideoHelper";
public static boolean canEnterPipMode(Context context) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
context.getString(R.string.pref_background_float_key);
// pref is disabled
if (!context.getString(R.string.pref_background_float_key).equals(
sharedPref.getString(
context.getString(R.string.pref_background_behavior_key),
context.getString(R.string.pref_background_float_key))
)
) {
return false;
}
// api does not support it
Log.v(TAG, "api version " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT > 27) {
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
return (AppOpsManager.MODE_ALLOWED == appOpsManager.checkOp(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName()));
}
return false;
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.intents;
@ -27,6 +26,7 @@ import android.os.Build;
import android.os.Environment;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import android.widget.Toast;
import com.github.se_bastiaan.torrentstream.TorrentOptions;
@ -40,6 +40,7 @@ import androidx.core.app.ActivityCompat;
public class Intents {
private static final String TAG = "Intents";
/**
* https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c
@ -68,20 +69,25 @@ public class Intents {
// TODO, offer which version to download
public static void Download(Context context, Video video) {
String url = video.getFiles().get(0).getFileDownloadUrl();
// make sure it is a valid filename
String destFilename = video.getName().replaceAll("[^a-zA-Z0-9]", "_") + "." + MimeTypeMap.getFileExtensionFromUrl(URLUtil.guessFileName(url,null,null));
if (video.getFiles().size() > 0)
{
String url = video.getFiles().get( 0 ).getFileDownloadUrl();
// make sure it is a valid filename
String destFilename = video.getName().replaceAll( "[^a-zA-Z0-9]", "_" ) + "." + MimeTypeMap.getFileExtensionFromUrl( URLUtil.guessFileName( url, null, null ) );
//Toast.makeText(context, destFilename, Toast.LENGTH_LONG).show();
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDescription(video.getDescription());
request.setTitle(video.getName());
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, destFilename);
//Toast.makeText(context, destFilename, Toast.LENGTH_LONG).show();
DownloadManager.Request request = new DownloadManager.Request( Uri.parse( url ) );
request.setDescription( video.getDescription() );
request.setTitle( video.getName() );
request.allowScanningByMediaScanner();
request.setNotificationVisibility( DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED );
request.setDestinationInExternalPublicDir( Environment.DIRECTORY_DOWNLOADS, destFilename );
// get download service and enqueue file
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
// get download service and enqueue file
DownloadManager manager = (DownloadManager) context.getSystemService( Context.DOWNLOAD_SERVICE );
manager.enqueue( request );
} else {
Toast.makeText( context, R.string.api_error, Toast.LENGTH_LONG ).show();
}
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;
public class Me {

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;
import com.google.gson.annotations.SerializedName;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,22 +1,24 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;
import java.util.ArrayList;
import java.util.Date;
public class Server {
private Integer id;
@ -26,16 +28,20 @@ public class Server {
private String version;
private Boolean signupAllowed;
private Double userVideoQuota;
private Category category;
private ArrayList<String> languages;
private Boolean autoBlacklistUserVideosEnabled;
private String defaultNSFWPolicy;
private Boolean isNSFW;
private Integer totalUsers;
private Integer totalVideos;
private Integer totalLocalVideos;
private Integer totalInstanceFollowers;
private Integer totalInstanceFollowing;
private Boolean supportsIPv6;
private String country;
private Integer health;
private Date createdAt;
public Integer getId() {
return id;
@ -93,6 +99,46 @@ public class Server {
this.userVideoQuota = userVideoQuota;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public ArrayList<String> getLanguages() {
return languages;
}
public void setLanguages(ArrayList<String> languages) {
this.languages = languages;
}
public Boolean getAutoBlacklistUserVideosEnabled() {
return autoBlacklistUserVideosEnabled;
}
public void setAutoBlacklistUserVideosEnabled(Boolean autoBlacklistUserVideosEnabled) {
this.autoBlacklistUserVideosEnabled = autoBlacklistUserVideosEnabled;
}
public String getDefaultNSFWPolicy() {
return defaultNSFWPolicy;
}
public void setDefaultNSFWPolicy(String defaultNSFWPolicy) {
this.defaultNSFWPolicy = defaultNSFWPolicy;
}
public Boolean getNSFW() {
return isNSFW;
}
public void setNSFW(Boolean NSFW) {
isNSFW = NSFW;
}
public Integer getTotalUsers() {
return totalUsers;
}
@ -156,4 +202,12 @@ public class Server {
public void setHealth(Integer health) {
this.health = health;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.model;

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import okhttp3.Authenticator;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
public class AccessTokenAuthenticator implements Authenticator {
private static final String TAG = "ATAuthenticator";
public AccessTokenAuthenticator() {
}
@Nullable
@Override
public Request authenticate(Route route, @NonNull Response response) {
Session session = Session.getInstance();
// check if we are using tokens
final String accessToken = session.getToken();
if (!isRequestWithAccessToken(response) || accessToken == null) {
return null;
}
synchronized (this) {
final String newAccessToken = session.getToken();
// Access token is refreshed in another thread.
if (!accessToken.equals(newAccessToken)) {
Log.v(TAG, "Access token is refreshed in another thread");
return newRequestWithAccessToken(response.request(), newAccessToken);
}
// do we have a refresh token?
if (session.getRefreshToken() == null) {
Log.v(TAG, "No refresh token available");
return null;
}
Log.v(TAG, "refresh token: " + session.getRefreshToken());
// Need to refresh an access token
Log.v(TAG, "Need to refresh an access token");
final String updatedAccessToken = session.refreshAccessToken();
if (updatedAccessToken != null) {
return newRequestWithAccessToken(response.request(), updatedAccessToken);
}
Log.v(TAG, "Refresh failed");
return null;
}
}
private boolean isRequestWithAccessToken(@NonNull Response response) {
String header = response.request().header("Authorization");
return header != null && header.startsWith("Bearer");
}
@NonNull
private Request newRequestWithAccessToken(@NonNull Request request, @NonNull String accessToken) {
return request.newBuilder()
.header("Authorization", accessToken)
.build();
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
@ -42,4 +41,14 @@ public interface AuthenticationService {
@Field("password") String password
);
@POST("users/token")
@FormUrlEncoded
Call<Token> refreshToken(
@Field("client_id") String clientId,
@Field("client_secret") String clientSecret,
@Field("grant_type") String grantType,
@Field("response_type") String responseType,
@Field("username") String username,
@Field("refresh_token") String refreshToken
);
}

View File

@ -1,25 +1,26 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.IOException;
import okhttp3.Interceptor;
@ -31,8 +32,7 @@ public class AuthorizationInterceptor implements Interceptor {
public AuthorizationInterceptor() {
}
@NonNull
@Override
public Response intercept(Chain chain) throws IOException {

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
@ -27,7 +26,8 @@ public interface GetServerListDataService {
@GET("instances/")
Call<ServerList> getInstancesData(
@Query("start") int start,
@Query("count") int count
@Query("count") int count,
@Query("search") String text
);
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
import net.schueller.peertube.model.Account;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;

View File

@ -1,22 +1,26 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
import static net.schueller.peertube.network.UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import net.schueller.peertube.R;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@ -26,13 +30,20 @@ public class RetrofitInstance {
private static Retrofit retrofit;
private static String baseUrl;
public static Retrofit getRetrofitInstance(String newBaseUrl) {
public static Retrofit getRetrofitInstance(String newBaseUrl, boolean insecure) {
if (retrofit == null || !newBaseUrl.equals(baseUrl)) {
baseUrl = newBaseUrl;
OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient.Builder();
OkHttpClient.Builder okhttpClientBuilder;
if (!insecure) {
okhttpClientBuilder = new OkHttpClient.Builder();
} else {
okhttpClientBuilder = getUnsafeOkHttpClientBuilder();
}
okhttpClientBuilder.addInterceptor(new AuthorizationInterceptor());
okhttpClientBuilder.authenticator(new AccessTokenAuthenticator());
retrofit = new retrofit2.Retrofit.Builder()
.client(okhttpClientBuilder.build())

View File

@ -1,43 +1,43 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import net.schueller.peertube.R;
import net.schueller.peertube.application.AppApplication;
import static net.schueller.peertube.service.LoginService.refreshToken;
public class Session {
private static volatile Session sSoleInstance;
private static SharedPreferences sharedPreferences;
//private constructor.
private Session(){
private Session() {
Context context = AppApplication.getContext();
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
//Prevent form the reflection api.
if (sSoleInstance != null){
if (sSoleInstance != null) {
throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
}
}
@ -58,7 +58,6 @@ public class Session {
}
public boolean isLoggedIn() {
// check if token exist or not
// return true if exist otherwise false
@ -69,13 +68,6 @@ public class Session {
return getToken() != null;
}
public void saveToken(String token) {
// save the token
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(AppApplication.getContext().getString(R.string.pref_token_access), token);
editor.commit();
}
public String getToken() {
// return the token that was saved earlier
@ -89,27 +81,24 @@ public class Session {
return null;
}
public void saveUsername(String username) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(AppApplication.getContext().getString(R.string.pref_auth_username), username);
editor.commit();
}
public String getEmail() {
return sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_auth_username), null);
}
public void savePassword(String password) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(AppApplication.getContext().getString(R.string.pref_auth_password), password);
editor.commit();
}
public String getPassword() {
return sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_auth_password), null);
}
public String getRefreshToken() {
return sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_token_refresh), null);
}
public String refreshAccessToken() {
refreshToken();
// refresh token
return this.getToken();
}
public void invalidate() {
// get called when user become logged out
// delete token and other user info
@ -122,6 +111,7 @@ public class Session {
editor.putString(context.getString(R.string.pref_auth_password), null);
editor.putString(context.getString(R.string.pref_auth_username), null);
editor.putString(context.getString(R.string.pref_token_access), null);
editor.putString(context.getString(R.string.pref_token_refresh), null);
editor.commit();
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.network;
import android.annotation.SuppressLint;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient;
import okhttp3.OkHttpClient.Builder;
// Take from https://stackoverflow.com/questions/25509296/trusting-all-certificates-with-okhttp/25992879#25992879
public class UnsafeOkHttpClient {
public static Builder getUnsafeOkHttpClientBuilder() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
builder.hostnameVerifier((hostname, session) -> true);
return builder;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.provider;

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.service;
@ -26,6 +25,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import net.schueller.peertube.R;
import net.schueller.peertube.application.AppApplication;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.model.OauthClient;
import net.schueller.peertube.model.Token;
@ -42,15 +42,14 @@ public class LoginService {
private static final String TAG = "LoginService";
public static void Authenticate(String username, String password)
{
public static void Authenticate(String username, String password) {
Context context = getContext();
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
AuthenticationService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(AuthenticationService.class);
AuthenticationService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(AuthenticationService.class);
Call<OauthClient> call = service.getOauthClientLocal();
@ -62,6 +61,14 @@ public class LoginService {
OauthClient oauthClient = response.body();
SharedPreferences.Editor editor = sharedPref.edit();
assert oauthClient != null;
editor.putString(context.getString(R.string.pref_client_id), oauthClient.getClientId());
editor.putString(context.getString(R.string.pref_client_secret), oauthClient.getClientSecret());
editor.apply();
Call<Token> call2 = service.getAuthenticationToken(
oauthClient.getClientId(),
oauthClient.getClientSecret(),
@ -79,13 +86,9 @@ public class LoginService {
Token token = response2.body();
SharedPreferences.Editor editor = sharedPref.edit();
// TODO: calc expiration
//editor.putInt(getString(R.string.pref_token_expiration), token.getRefreshToken());
assert token != null;
editor.putString(context.getString(R.string.pref_token_access), token.getAccessToken());
editor.putString(context.getString(R.string.pref_token_refresh), token.getExpiresIn());
editor.putString(context.getString(R.string.pref_token_refresh), token.getRefreshToken());
editor.putString(context.getString(R.string.pref_token_type), token.getTokenType());
editor.apply();
@ -124,4 +127,62 @@ public class LoginService {
}
});
}
public static void refreshToken() {
Context context = getContext();
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
AuthenticationService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(AuthenticationService.class);
String refreshToken = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_token_refresh), null);
String userName = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_auth_username), null);
String clientId = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_client_id), null);
String clientSecret = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_client_secret), null);
Call<Token> call = service.refreshToken(
clientId,
clientSecret,
"refresh_token",
"code",
userName,
refreshToken
);
call.enqueue(new Callback<Token>() {
@Override
public void onResponse(@NonNull Call<Token> call, @NonNull retrofit2.Response<Token> response) {
if (response.isSuccessful()) {
Token token = response.body();
SharedPreferences.Editor editor = sharedPreferences.edit();
assert token != null;
editor.putString(context.getString(R.string.pref_token_access), token.getAccessToken());
editor.putString(context.getString(R.string.pref_token_refresh), token.getRefreshToken());
editor.putString(context.getString(R.string.pref_token_type), token.getTokenType());
editor.apply();
Log.wtf(TAG, "Logged in");
Toast.makeText(context, context.getString(R.string.authentication_token_refresh_success), Toast.LENGTH_LONG).show();
} else {
Log.wtf(TAG, response.toString());
Toast.makeText(context, context.getString(R.string.authentication_token_refresh_failed), Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(@NonNull Call<Token> call2, @NonNull Throwable t2) {
Log.wtf("err", t2.fillInStackTrace());
Toast.makeText(context, context.getString(R.string.authentication_token_refresh_failed), Toast.LENGTH_LONG).show();
}
});
}
}

View File

@ -1,19 +1,18 @@
/*
* Copyright 2018 Stefan Schüller <sschueller@techdroid.com>
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.schueller.peertube.service;
@ -33,6 +32,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.webkit.URLUtil;
import androidx.annotation.Nullable;
import android.support.v4.media.MediaDescriptionCompat;
@ -50,6 +50,8 @@ import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource;
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
@ -59,32 +61,39 @@ import com.google.android.exoplayer2.util.Util;
import net.schueller.peertube.R;
import net.schueller.peertube.activity.VideoPlayActivity;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.MetaDataHelper;
import net.schueller.peertube.model.Video;
import okhttp3.OkHttpClient;
import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
import static net.schueller.peertube.activity.VideoListActivity.EXTRA_VIDEOID;
import static net.schueller.peertube.network.UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder;
public class VideoPlayerService extends Service {
private static final String TAG = "VideoPlayerService";
private static final String MEDIA_SESSION_TAG = "peertube_player";
private final IBinder mBinder = new LocalBinder();
private static final String PLAYBACK_CHANNEL_ID = "playback_channel";
private static final Integer PLAYBACK_NOTIFICATION_ID = 1;
public SimpleExoPlayer player;
private Video currentVideo;
private String currentStreamUrl;
private PlayerNotificationManager playerNotificationManager;
private IntentFilter becomeNoisyIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver();
@Override
@ -105,13 +114,13 @@ public class VideoPlayerService extends Service {
registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter);
}
if (playbackState == ACTION_PLAY) { // this means that play is available, hence the audio is paused or stopped
if (playbackState
== ACTION_PLAY) { // this means that play is available, hence the audio is paused or stopped
Log.v(TAG, "ACTION_PAUSE: " + playbackState);
unregisterReceiver(myNoisyAudioStreamReceiver);
myNoisyAudioStreamReceiver=null;
safeUnregisterReceiver();
}
}
} );
});
}
@ -131,14 +140,9 @@ public class VideoPlayerService extends Service {
if (playerNotificationManager != null) {
playerNotificationManager.setPlayer(null);
}
//Was seeing an error when exiting the program about about not unregistering the receiver.
try {
if (null!=myNoisyAudioStreamReceiver) {
this.unregisterReceiver(myNoisyAudioStreamReceiver);
}
} catch (Exception e) {
Log.e("VideoPlayerService", "attempted to unregister a nonregistered service");
}
//Was seeing an error when exiting the program about not unregistering the receiver.
safeUnregisterReceiver();
if (player != null) {
player.release();
player = null;
@ -146,6 +150,15 @@ public class VideoPlayerService extends Service {
super.onDestroy();
}
private void safeUnregisterReceiver()
{
try {
unregisterReceiver(myNoisyAudioStreamReceiver);
} catch (Exception e) {
Log.e("VideoPlayerService", "attempted to unregister a nonregistered service");
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
@ -156,41 +169,62 @@ public class VideoPlayerService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
Context context = this;
Log.v(TAG, "onStartCommand...");
if(currentStreamUrl == null){
Toast.makeText(context, "currentStreamUrl must not null", Toast.LENGTH_SHORT).show();
if (!URLUtil.isValidUrl(currentStreamUrl)) {
Toast.makeText(context, "Invalid URL provided. Unable to play video.", Toast.LENGTH_SHORT).show();
return START_NOT_STICKY;
} else {
playVideo();
return START_STICKY;
}
playVideo();
return START_STICKY;
}
public void setCurrentVideo(Video video)
{
public void setCurrentVideo(Video video) {
Log.v(TAG, "setCurrentVideo...");
currentVideo = video;
}
public void setCurrentStreamUrl(String streamUrl)
{
public void setCurrentStreamUrl(String streamUrl) {
Log.v(TAG, "setCurrentStreamUrl..." + streamUrl);
currentStreamUrl = streamUrl;
}
//Playback speed control
public void setPlayBackSpeed(float speed) {
Log.v(TAG, "setPlayBackSpeed...");
player.setPlaybackParameters(new PlaybackParameters(speed));
}
/**
* Returns the current playback speed of the player.
*
* @return the current playback speed of the player.
*/
public float getPlayBackSpeed() {
return player.getPlaybackParameters().speed;
}
public void playVideo() {
Context context = this;
// We need a valid URL
Log.v(TAG, "playVideo...");
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
Util.getUserAgent(getApplicationContext(), "PeerTube"), null);
// DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
// Util.getUserAgent(getApplicationContext(), "PeerTube"), null);
OkHttpClient.Builder okhttpClientBuilder;
if (!APIUrlHelper.useInsecureConnection(this)) {
okhttpClientBuilder = new OkHttpClient.Builder();
} else {
okhttpClientBuilder = getUnsafeOkHttpClientBuilder();
}
DataSource.Factory dataSourceFactory = new OkHttpDataSourceFactory(okhttpClientBuilder.build(), Util.getUserAgent(getApplicationContext(), "PeerTube"));
// This is the MediaSource representing the media to be played.
ExtractorMediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
@ -235,7 +269,8 @@ public class VideoPlayerService extends Service {
@Nullable
@Override
public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
public Bitmap getCurrentLargeIcon(Player player,
PlayerNotificationManager.BitmapCallback callback) {
return null;
}
}
@ -290,15 +325,16 @@ public class VideoPlayerService extends Service {
// Audio Focus
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MOVIE)
.build();
player.setAudioAttributes(audioAttributes,true);
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MOVIE)
.build();
player.setAudioAttributes(audioAttributes, true);
}
// pause playback on audio output change
private class BecomingNoisyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {

View File

@ -0,0 +1,58 @@
package net.schueller.peertube.utils
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
fun View.gone() {
this.visibility = View.GONE
}
fun View.visible() {
this.visibility = View.VISIBLE
}
fun View.invisible() {
this.visibility = View.INVISIBLE
}
fun View.visibleIf(predicate: () -> Boolean) {
when (predicate.invoke()) {
true -> visible()
else -> gone()
}
}
fun View.goneIf(predicate: () -> Boolean) {
when (predicate.invoke()) {
true -> gone()
else -> visible()
}
}
fun View.invisibleIf(predicate: () -> Boolean) {
when (predicate.invoke()) {
true -> invisible()
else -> visible()
}
}
fun Fragment.hideKeyboard(): Boolean {
activity?.currentFocus?.let {
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(it.windowToken, 0)
return true
}
return false
}
fun AppCompatActivity.hideKeyboard(): Boolean {
currentFocus?.let {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(it.windowToken, 0)
return true
}
return false
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -4,7 +4,12 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.SelectServerActivity">
tools:context=".activity.SearchServerActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_server_selection"
@ -29,11 +34,19 @@
android:text="@string/no_data_available"
android:visibility="gone" />
<EditText
android:id="@+id/search_server_input_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_selection_filter_hint"
android:singleLine="true"
android:imeOptions="actionGo"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/serversSwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/appbar_server_selection"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
@ -45,5 +58,5 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -31,7 +31,25 @@
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_baseline_add_24" />
<include layout="@layout/content_server_address_book" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/server_list_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
tools:listitem="@layout/row_server_address_book"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,6 +1,21 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/settings"

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".activity.ServerAddressBookActivity"
tools:showIn="@layout/activity_server_address_book">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/server_list_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
tools:listitem="@layout/row_serverbook" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -19,28 +19,29 @@
android:hint="@string/server_book_add_label"
android:inputType="textPersonName" />
<RelativeLayout
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content">
android:layout_width="match_parent"
android:orientation="horizontal"
>
<EditText
android:layout_alignParentStart="true"
android:id="@+id/serverUrl"
android:layout_width="fill_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:hint="@string/server_book_add_server_url"
android:inputType="textUri"
android:layout_toStartOf="@+id/pickServerUrl"/>
/>
<Button
android:layout_alignParentEnd="true"
android:id="@+id/pickServerUrl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_book_add_pick_server_button" />
</RelativeLayout>
</LinearLayout>
<EditText
android:id="@+id/serverUsername"

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="6dp">
<de.hdodenhof.circleimageview.CircleImageView xmlns:app="http://schemas.android.com/apk/res-auto"
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatar"
android:layout_width="72dp"
android:layout_height="72dp"
@ -16,7 +17,7 @@
android:paddingEnd="12dp" />
<TextView
android:id="@+id/name"
android:id="@+id/sl_row_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
@ -31,7 +32,7 @@
android:id="@+id/videoMeta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_below="@+id/sl_row_name"
android:layout_alignParentEnd="true"
android:layout_marginStart="6dp"
android:layout_marginTop="0dp"
@ -58,7 +59,7 @@
android:layout_marginStart="-16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="0dp"
android:layout_toEndOf="@+id/name"
android:layout_toEndOf="@+id/sl_row_name"
android:background="@null"
android:contentDescription="@string/descr_overflow_button"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
@ -194,6 +195,7 @@
android:layout_marginStart="18dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:autoLink="web"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
<LinearLayout
@ -209,7 +211,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="bottom"
android:orientation="horizontal">
<TextView
@ -228,8 +230,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center|start"
android:text=""
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
tools:text="@tools:sample/lorem/random"
android:lines="2"
/>
</LinearLayout>
@ -238,7 +242,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="bottom"
android:orientation="horizontal">
<TextView
@ -257,8 +261,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center|start"
android:text=""
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
tools:text="@tools:sample/lorem/random"
android:lines="2"
/>
</LinearLayout>
@ -266,7 +272,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="bottom"
android:orientation="horizontal">
<TextView
@ -285,8 +291,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center|start"
android:text=""
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
tools:text="@tools:sample/lorem"
android:lines="2"
/>
</LinearLayout>
@ -295,7 +303,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="bottom"
android:orientation="horizontal">
<TextView
@ -314,8 +322,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center|start"
android:text=""
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
tools:text="@tools:sample/lorem/random"
android:lines="2"
/>
</LinearLayout>
@ -323,7 +333,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="bottom"
android:orientation="horizontal">
<TextView
@ -342,7 +352,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center|start"
android:text=""
tools:text="@tools:sample/lorem/random"
android:lines="2"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
</LinearLayout>

View File

@ -50,7 +50,7 @@
android:paddingEnd="12dp" />
<TextView
android:id="@+id/name"
android:id="@+id/sl_row_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/thumb"
@ -66,7 +66,7 @@
android:id="@+id/videoMeta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_below="@+id/sl_row_name"
android:layout_marginStart="6dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="6dp"
@ -93,7 +93,7 @@
android:layout_marginTop="16dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="0dp"
android:layout_toEndOf="@+id/name"
android:layout_toEndOf="@+id/sl_row_name"
android:background="@null"
android:contentDescription="@string/descr_overflow_button"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"

View File

@ -50,7 +50,7 @@
android:paddingEnd="12dp" />
<TextView
android:id="@+id/name"
android:id="@+id/sl_row_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/thumb"
@ -66,7 +66,7 @@
android:id="@+id/videoMeta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_below="@+id/sl_row_name"
android:layout_marginStart="6dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="6dp"
@ -93,7 +93,7 @@
android:layout_marginTop="16dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="0dp"
android:layout_toEndOf="@+id/name"
android:layout_toEndOf="@+id/sl_row_name"
android:background="@null"
android:contentDescription="@string/descr_overflow_button"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"

View File

@ -50,7 +50,7 @@
android:paddingEnd="12dp" />
<TextView
android:id="@+id/name"
android:id="@+id/sl_row_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/thumb"
@ -66,7 +66,7 @@
android:id="@+id/videoMeta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_below="@+id/sl_row_name"
android:layout_marginStart="6dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="6dp"
@ -93,7 +93,7 @@
android:layout_marginTop="16dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="0dp"
android:layout_toEndOf="@+id/name"
android:layout_toEndOf="@+id/sl_row_name"
android:background="@null"
android:contentDescription="@string/descr_overflow_button"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"

View File

@ -7,15 +7,18 @@
card_view:cardElevation="0dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
android:orientation="vertical">
<TextView
android:id="@+id/name"
android:id="@+id/sl_row_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
@ -24,7 +27,7 @@
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline" />
<TextView
android:id="@+id/host"
android:id="@+id/sl_row_host"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
@ -33,7 +36,7 @@
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/signupAllowed"
android:id="@+id/sl_row_signup_allowed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
@ -43,14 +46,45 @@
<TextView
android:id="@+id/shortDescription"
android:id="@+id/sl_row_short_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginEnd="24dp"
android:paddingTop="0dp"
android:visibility="gone"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="0dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="24dp"
android:orientation="horizontal">
<TextView
android:id="@+id/sl_row_video_totals"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/sl_row_is_nsfw"
android:src="@drawable/ic_baseline_remove_red_eye_24"
android:visibility="gone"
android:layout_width="24dp"
android:layout_alignParentEnd="true"
android:layout_height="wrap_content"
android:contentDescription="@string/server_selection_nsfw_instance" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
card_view:cardCornerRadius="0dp"
card_view:cardElevation="0dp"
card_view:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="12dp">
<TextView
android:id="@+id/serverLabelRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:layout_constraintTop_toTopOf="parent"
card_view:layout_constraintStart_toStartOf="parent"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline"
tools:text="@tools:sample/lorem"
/>
<TextView
android:id="@+id/serverUrlRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:layout_constraintTop_toBottomOf="@id/serverLabelRow"
card_view:layout_constraintStart_toStartOf="parent"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead"
tools:text="@tools:sample/lorem"
/>
<ImageView
android:id="@+id/sb_row_has_login_icon"
android:layout_width="24dp"
android:layout_height="wrap_content"
card_view:layout_constraintTop_toTopOf="parent"
card_view:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/server_book_list_has_login"
android:src="@drawable/ic_baseline_account_circle_24"
/>
<ImageView
android:id="@+id/edit_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
card_view:layout_constraintTop_toBottomOf="@id/sb_row_has_login_icon"
card_view:layout_constraintEnd_toEndOf="parent"
card_view:layout_constraintBottom_toBottomOf="parent"
android:contentDescription="@string/server_book_list_has_login"
android:src="@drawable/ic_edit_24"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
card_view:cardCornerRadius="0dp"
card_view:cardElevation="0dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="12dp">
<LinearLayout
android:layout_alignParentStart="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="@+id/serverLabelRow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline" />
<TextView
android:id="@+id/serverUrlRow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="0dp"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<ImageView
android:id="@+id/sb_row_has_login_icon"
android:src="@drawable/ic_baseline_account_circle_24"
android:visibility="visible"
android:contentDescription="@string/server_book_list_has_login"
android:layout_width="24dp"
android:layout_alignParentEnd="true"
android:layout_height="wrap_content"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@ -58,7 +58,7 @@
android:paddingEnd="12dp" />
<TextView
android:id="@+id/name"
android:id="@+id/sl_row_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/thumb"
@ -74,7 +74,7 @@
android:id="@+id/videoMeta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_below="@+id/sl_row_name"
android:layout_marginStart="6dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="6dp"
@ -101,7 +101,7 @@
android:layout_marginTop="16dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="0dp"
android:layout_toEndOf="@+id/name"
android:layout_toEndOf="@+id/sl_row_name"
android:background="@null"
android:contentDescription="@string/descr_overflow_button"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"

View File

@ -24,7 +24,7 @@
<!-- Strings related to Settings -->
<string name="pref_title_peertube_server">خادوم PeerTube</string>
<!-- Strings related to Video meta data -->
<string name="meta_data_views">\u0020 مشاهدات</string>
<string name="meta_data_views">" مشاهدات"</string>
<string name="video_row_video_thumbnail">الصورة المصغرة للفيديو</string>
<string name="video_row_account_avatar">الصورة الرمزية للحساب</string>
<string name="pref_title_show_nsfw">عرض NSFW</string>
@ -143,8 +143,8 @@
<string name="video_meta_button_license">الرخصة</string>
<string name="video_meta_button_language">اللغة</string>
<string name="video_meta_button_tags">العلامات</string>
<string name="menu_video_options_playback_speed">سرعة التشغيل</string>
<string name="menu_video_options_quality">الجودة</string>
<string name="menu_video_options_playback_speed" formatted="true">سرعة التشغيل (%1$s)</string>
<string name="menu_video_options_quality" formatted="true">(%1$s) الجودة</string>
<string name="account_bottom_menu_videos">الفيديو</string>
<string name="account_bottom_menu_channels">القنوات</string>
<string name="account_bottom_menu_about">حول</string>
@ -305,4 +305,51 @@
<string name="login_current_server_hint">الخادوم الحالي</string>
<string name="video_speed_075">0.75x</string>
<string name="video_speed_125">1.25x</string>
<string name="pref_description_language_app">حدد اللغة لواجهة التطبيق. أعد تشغيل التطبيق حتى يسري التغيير.</string>
<string name="menu_video_options_quality_automated">الآلي</string>
<string name="server_selection_video_totals">مقاطع الفيديو: %s، مقاطع الفيديو المحلية: %s</string>
<string name="server_selection_nsfw_instance">خادم NSFW</string>
<string name="settings_activity_look_and_feel_category_title">الشكل والمظهر</string>
<string name="settings_activity_about_category_title">حول</string>
<string name="settings_activity_video_playback_category_title">تشغيل الفيديو</string>
<string name="settings_activity_video_list_category_title">قائمة الفيديو</string>
<string name="title_activity_settings2">إعدادات النشاط2</string>
<string name="title_activity_me">حساب</string>
<string name="title_activity_select_server">خادم البحث</string>
<string name="server_book_del_alert_msg">هل تريد بالتأكيد إزالة هذا الملقم من دفتر العناوين؟</string>
<string name="server_book_del_alert_title">إزالة الملقم</string>
<string name="title_activity_server_address_book">دليل العناوين</string>
<string name="server_book_list_has_login">لديه تسجيل الدخول</string>
<string name="server_book_add_add_button">إضافة</string>
<string name="server_book_add_password">كلمة المرور</string>
<string name="server_book_add_username">اسم المستخدم</string>
<string name="server_book_add_pick_server_button">بحث</string>
<string name="server_book_add_server_url">عنوان URL للملقم</string>
<string name="server_book_add_label">تسميه</string>
<string name="me_help_and_feedback_button">المساعدة والتعليقات</string>
<string name="me_logout_button">تسجيل الخروج</string>
<string name="server_book_valid_url_is_required">مطلوب URL صالح</string>
<string name="server_book_label_is_required">تسمية الملقم مطلوبة</string>
<string name="authentication_login_failed">فشل تسجيل الدخول!</string>
<string name="authentication_login_success">تم تسجيل الدخول</string>
<string name="hello_blank_fragment">مرحبا جزء فارغ</string>
<string name="bn_rBD">البنغالية (بنغلاديش)</string>
<string name="clear_search_history_prompt">هل تريد حذف سجل البحث بشكل دائم؟</string>
<string name="clear_search_history">مسح سجل البحث</string>
<string name="pref_background_behavior_summary">كيف يستجيب الفيديو أثناء التشغيل عند الانتقال إلى الخلفية</string>
<string name="settings_permissions_error_float">تم تعطيل إذن صورة في صورة لهذا التطبيق في إعدادات Android</string>
<string name="settings_api_error_float">إصدار Android لا يدعم الفيديو العائم</string>
<string name="pref_background_float">استمر في تشغيل الفيديو في النافذة العائمة</string>
<string name="pref_language_app">لغة التطبيق</string>
<string name="pref_description_back_pause">أوقف التشغيل في الخلفية مؤقتًا عند الضغط على رجوع أثناء تشغيل الفيديو.</string>
<string name="pref_title_back_pause">ايقاف عند الضغط على زر العودة</string>
<string name="pref_title_buildtime">تاريخ ووقت البناء</string>
<string name="network_error">خطأ في الوصول للشبكة، تحقق من اتصالك</string>
<string name="server_selection_filter_hint">فلترة القائمة</string>
<string name="pref_background_behavior">إعدادات التشغيل في الخلفية</string>
<string name="pref_background_stop">إيقاف تشغيل الكل</string>
<string name="pref_background_audio">تابع تشغيل الصوت في الخلفية</string>
<string name="authentication_token_refresh_success">تم تحديث الرمز</string>
<string name="authentication_token_refresh_failed">تعذر تحديث الرمز المميز</string>
<string name="server_book_no_servers_found">كتب الخادم فارغة</string>
</resources>

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