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) ### Version 1.0.41 Tag: v1.0.41 (2020-06-28)
* Floating window player controls fix (@dhk2) * Floating window player controls fix (@dhk2)
* Updated app icons * 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"> <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://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://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://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> <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> </p>
## Screenshots ## 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/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/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.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 ## 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. 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 * 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/ * 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 ## Help Translate
* https://hosted.weblate.org/projects/peertube/ * 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 speed selection
* Video quality selection * Video quality selection
* Server selection * Server selection
* Video overlay play and draggable video window
## Coming soon ## Coming soon
* Video Playback via WebRTC * Video Playback via WebRTC
* Video overlay play and draggable video window
* Comment videos * Comment videos
* Report Videos * Report Videos
* User / Channel Overview Page * User / Channel Overview Page

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 { android {
compileSdkVersion 29 compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig { defaultConfig {
applicationId "net.schueller.peertube" applicationId "net.schueller.peertube"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 1041 versionCode Integer.valueOf(System.getenv("VERSION_CODE") ?: 1)
versionName "1.0.41" versionName System.getenv("VERSION_NAME") + "-" + System.getenv("VERSION_SHA")
buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ext {
libVersions = [
exoplayer: '2.11.6'
]
}
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments = [ arguments = [
@ -22,56 +57,24 @@ android {
"room.expandProjection": "true"] "room.expandProjection": "true"]
} }
} }
dependencies { }
implementation fileTree(dir: 'libs', include: ['*.jar']) signingConfigs {
release {
// Layouts and design // You need to specify either an absolute path or include the
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' // keystore file in the same directory as the build.gradle file.
implementation 'androidx.appcompat:appcompat:1.1.0' storeFile file("../android-signing-keystore.jks")
implementation 'androidx.cardview:cardview:1.0.0' storePassword "${secretProperties['signing_keystore_password']}"
implementation 'androidx.recyclerview:recyclerview:1.1.0' keyAlias "${secretProperties['signing_key_alias']}"
implementation 'androidx.legacy:legacy-support-v13:1.0.0' keyPassword "${secretProperties['signing_key_password']}"
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'
} }
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
testCoverageEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
} }
} }
compileOptions { compileOptions {
@ -82,28 +85,91 @@ android {
applicationVariants.all { variant -> applicationVariants.all { variant ->
variant.resValue "string", "versionName", variant.versionName 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 { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
def room_version = "2.2.5" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
def archLifecycleVersion = '2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7' // Layouts and design
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.1.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 // database lib
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation "androidx.room:room-ktx:$room_version"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' kapt "androidx.room:room-compiler:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
androidTestImplementation "androidx.room:room-testing:$room_version"
// Lifecycle components // Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$archLifecycleVersion" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$archLifecycleVersion" 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:label="@string/title_activity_settings"
android:theme="@style/AppTheme.NoActionBar" /> <!-- Server Selection --> android:theme="@style/AppTheme.NoActionBar" /> <!-- Server Selection -->
<activity <activity
android:name=".activity.SelectServerActivity" android:name=".activity.SearchServerActivity"
android:label="@string/title_activity_select_server" android:label="@string/title_activity_select_server"
android:theme="@style/AppTheme.NoActionBar" /> <!-- Me --> android:theme="@style/AppTheme.NoActionBar" /> <!-- Me -->
<activity <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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.activity; 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.ChannelAdapter;
import net.schueller.peertube.adapter.VideoAdapter; import net.schueller.peertube.adapter.VideoAdapter;
import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.helper.MetaDataHelper; import net.schueller.peertube.helper.MetaDataHelper;
import net.schueller.peertube.model.Account; import net.schueller.peertube.model.Account;
import net.schueller.peertube.model.Avatar; import net.schueller.peertube.model.Avatar;
@ -96,7 +96,7 @@ public class AccountActivity extends CommonActivity {
apiBaseURL = APIUrlHelper.getUrlWithVersion(this); 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); recyclerViewVideos = findViewById(R.id.account_video_recyclerView);
recyclerViewChannels = findViewById(R.id.account_channel_recyclerView); recyclerViewChannels = findViewById(R.id.account_channel_recyclerView);
@ -206,7 +206,7 @@ public class AccountActivity extends CommonActivity {
} }
} else { } 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 @Override
public void onFailure(@NonNull Call<Account> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<Account> call, @NonNull Throwable t) {
Log.wtf(TAG, t.fillInStackTrace()); 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; 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<VideoList> call;
call = service.getAccountVideosData(displayNameAndHost, videosStart, videosCount, videosSort); call = service.getAccountVideosData(displayNameAndHost, videosStart, videosCount, videosSort);
@ -247,8 +247,7 @@ public class AccountActivity extends CommonActivity {
} }
} else{ } else{
Toast.makeText(AccountActivity.this, getString(R.string.api_error), Toast.LENGTH_SHORT).show(); ErrorHelper.showToastFromCommunicationError( AccountActivity.this, null );
} }
isLoadingVideos = false; isLoadingVideos = false;
@ -258,7 +257,7 @@ public class AccountActivity extends CommonActivity {
@Override @Override
public void onFailure(@NonNull Call<VideoList> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<VideoList> call, @NonNull Throwable t) {
Log.wtf("err", t.fillInStackTrace()); 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; isLoadingVideos = false;
swipeRefreshLayoutVideos.setRefreshing(false); swipeRefreshLayoutVideos.setRefreshing(false);
} }
@ -281,7 +280,7 @@ public class AccountActivity extends CommonActivity {
} else { } 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 @Override
public void onFailure(@NonNull Call<ChannelList> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ChannelList> call, @NonNull Throwable t) {
Log.wtf(TAG, t.fillInStackTrace()); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.appcompat.app.AppCompatActivity; import net.schueller.peertube.R;
import androidx.appcompat.app.AppCompatDelegate;
import java.util.Locale; import java.util.Locale;
import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; import androidx.appcompat.app.AppCompatActivity;
import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; import androidx.appcompat.app.AppCompatDelegate;
public class CommonActivity extends AppCompatActivity { public class CommonActivity extends AppCompatActivity {
@ -39,30 +38,45 @@ public class CommonActivity extends AppCompatActivity {
// Set Night Mode // Set Night Mode
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); 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); AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
// Set theme // Set theme
setTheme(getResources().getIdentifier( 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", "style",
getPackageName()) getPackageName())
); );
// Set language // Set language
String countryCode=sharedPref.getString("pref_language_app","en"); String countryCode = sharedPref.getString(getString(R.string.pref_language_app_key), null);
Locale locale=new Locale(countryCode);;
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 //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; locale = Locale.SIMPLIFIED_CHINESE;
if(countryCode.equals("zh-rTW")) if (languageCode.equals("zh-rTW"))
locale = Locale.TRADITIONAL_CHINESE; locale = Locale.TRADITIONAL_CHINESE;
Locale.setDefault(locale); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
@ -31,6 +30,7 @@ import android.widget.TextView;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.model.Avatar; import net.schueller.peertube.model.Avatar;
import net.schueller.peertube.model.Me; import net.schueller.peertube.model.Me;
import net.schueller.peertube.network.GetUserService; import net.schueller.peertube.network.GetUserService;
@ -118,7 +118,7 @@ public class MeActivity extends CommonActivity {
String apiBaseURL = APIUrlHelper.getUrlWithVersion(this); String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
String baseURL = APIUrlHelper.getUrl(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(); Call<Me> call = service.getMe();
@ -162,6 +162,7 @@ public class MeActivity extends CommonActivity {
@Override @Override
public void onFailure(@NonNull Call<Me> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<Me> call, @NonNull Throwable t) {
ErrorHelper.showToastFromCommunicationError( MeActivity.this, t );
account.setVisibility(View.GONE); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -27,40 +25,37 @@ import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.util.Patterns; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.widget.Button; import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.adapter.ServerAdapter; import net.schueller.peertube.adapter.ServerSearchAdapter;
import net.schueller.peertube.adapter.VideoAdapter;
import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.model.ServerList; import net.schueller.peertube.model.ServerList;
import net.schueller.peertube.model.VideoList;
import net.schueller.peertube.network.GetServerListDataService; import net.schueller.peertube.network.GetServerListDataService;
import net.schueller.peertube.network.GetVideoDataService;
import net.schueller.peertube.network.RetrofitInstance; import net.schueller.peertube.network.RetrofitInstance;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; public class SearchServerActivity extends CommonActivity {
import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY;
public class SelectServerActivity extends CommonActivity { private ServerSearchAdapter serverAdapter;
private ServerAdapter serverAdapter;
private SwipeRefreshLayout swipeRefreshLayout; private SwipeRefreshLayout swipeRefreshLayout;
private EditText searchTextView;
private final static String TAG = "SearchServerActivity";
private int currentStart = 0; private int currentStart = 0;
private int count = 12; private final int count = 12;
private String lastSearchtext = "";
private TextView emptyView; private TextView emptyView;
private RecyclerView recyclerView; private RecyclerView recyclerView;
@ -76,7 +71,7 @@ public class SelectServerActivity extends CommonActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server_selection); setContentView(R.layout.activity_search_server);
// Attaching the layout to the toolbar object // Attaching the layout to the toolbar object
Toolbar toolbar = findViewById(R.id.tool_bar_server_selection); 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() { private void loadList() {
recyclerView = findViewById(R.id.serverRecyclerView); recyclerView = findViewById(R.id.serverRecyclerView);
swipeRefreshLayout = findViewById(R.id.serversSwipeRefreshLayout); swipeRefreshLayout = findViewById(R.id.serversSwipeRefreshLayout);
searchTextView = findViewById(R.id.search_server_input_field );
searchTextView.setOnEditorActionListener( onSearchTextValidated );
emptyView = findViewById(R.id.empty_server_selection_view); 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); recyclerView.setLayoutManager(layoutManager);
serverAdapter = new ServerAdapter(new ArrayList<>(), this); serverAdapter = new ServerSearchAdapter(new ArrayList<>(), this);
recyclerView.setAdapter(serverAdapter); recyclerView.setAdapter(serverAdapter);
loadServers(currentStart, count); loadServers(currentStart, count, searchTextView.getText().toString() );
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override @Override
@ -119,7 +123,7 @@ public class SelectServerActivity extends CommonActivity {
if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN)) { if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN)) {
if (!isLoading) { if (!isLoading) {
currentStart = currentStart + count; currentStart = currentStart + count;
loadServers(currentStart, count); loadServers(currentStart, count, searchTextView.getText().toString());
} }
} }
} }
@ -131,26 +135,29 @@ public class SelectServerActivity extends CommonActivity {
// Refresh items // Refresh items
if (!isLoading) { if (!isLoading) {
currentStart = 0; currentStart = 0;
loadServers(currentStart, count); loadServers(currentStart, count, searchTextView.getText().toString());
} }
}); });
} }
private void loadServers(int start, int count, String searchtext) {
private void loadServers(int start, int count) {
isLoading = true; isLoading = true;
GetServerListDataService service = RetrofitInstance.getRetrofitInstance( GetServerListDataService service = RetrofitInstance.getRetrofitInstance(
APIUrlHelper.getServerIndexUrl(SelectServerActivity.this) APIUrlHelper.getServerIndexUrl(SearchServerActivity.this)
).create(GetServerListDataService.class); , APIUrlHelper.useInsecureConnection(this)).create(GetServerListDataService.class);
if ( !searchtext.equals( lastSearchtext ) )
{
currentStart = 0;
lastSearchtext = searchtext;
}
Call<ServerList> call; Call<ServerList> call;
call = service.getInstancesData(start, count); call = service.getInstancesData(start, count, searchtext);
Log.d("URL Called", call.request().url() + ""); Log.d("URL Called", call.request().url() + "");
@ -183,7 +190,7 @@ public class SelectServerActivity extends CommonActivity {
@Override @Override
public void onFailure(@NonNull Call<ServerList> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ServerList> call, @NonNull Throwable t) {
Log.wtf("err", t.fillInStackTrace()); 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; isLoading = false;
swipeRefreshLayout.setRefreshing(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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import net.schueller.peertube.BuildConfig;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import java.util.Objects;
public class SettingsActivity extends CommonActivity { public class SettingsActivity extends CommonActivity {
@Override @Override
@ -38,16 +42,72 @@ public class SettingsActivity extends CommonActivity {
.beginTransaction() .beginTransaction()
.replace(R.id.settings, new SettingsFragment()) .replace(R.id.settings, new SettingsFragment())
.commit(); .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(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true); 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 { public static class SettingsFragment extends PreferenceFragmentCompat {
@Override @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
@ -50,7 +49,6 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -60,6 +58,8 @@ import com.mikepenz.iconics.IconicsDrawable;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.adapter.VideoAdapter; import net.schueller.peertube.adapter.VideoAdapter;
import net.schueller.peertube.helper.APIUrlHelper; 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.model.VideoList;
import net.schueller.peertube.network.GetUserService; import net.schueller.peertube.network.GetUserService;
import net.schueller.peertube.network.GetVideoDataService; import net.schueller.peertube.network.GetVideoDataService;
@ -70,6 +70,8 @@ import net.schueller.peertube.service.VideoPlayerService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import retrofit2.Call; import retrofit2.Call;
@ -320,17 +322,24 @@ public class VideoListActivity extends CommonActivity {
isLoading = true; isLoading = true;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false"; String nsfw = sharedPref.getBoolean(getString(R.string.pref_show_nsfw_key), false) ? "both" : "false";
Set<String> languages = sharedPref.getStringSet("pref_language", null);
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); 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; Call<VideoList> call;
if (!searchQuery.equals("")) { if (!searchQuery.equals("")) {
call = service.searchVideosData(start, count, sort, nsfw, searchQuery, filter, languages); call = service.searchVideosData(start, count, sort, nsfw, searchQuery, filter, languages);
} else if (subscriptions) { } 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); call = userService.getVideosSubscripions(start, count, sort);
} else { } else {
call = service.getVideosData(start, count, sort, nsfw, filter, languages); call = service.getVideosData(start, count, sort, nsfw, filter, languages);
@ -349,8 +358,11 @@ public class VideoListActivity extends CommonActivity {
} }
if (response.body() != null) { if (response.body() != null) {
ArrayList<Video> videoList = response.body().getVideoArrayList();
if (videoList != null) {
videoAdapter.setData(response.body().getVideoArrayList()); videoAdapter.setData(response.body().getVideoArrayList());
} }
}
// no results show no results message // no results show no results message
if (currentStart == 0 && videoAdapter.getItemCount() == 0) { if (currentStart == 0 && videoAdapter.getItemCount() == 0) {
@ -369,7 +381,7 @@ public class VideoListActivity extends CommonActivity {
@Override @Override
public void onFailure(@NonNull Call<VideoList> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<VideoList> call, @NonNull Throwable t) {
Log.wtf("err", t.fillInStackTrace()); 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; isLoading = false;
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
} }
@ -383,7 +395,7 @@ public class VideoListActivity extends CommonActivity {
// only check when we actually need the permission // only check when we actually need the permission
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && 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); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
@ -36,6 +35,7 @@ import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@ -56,26 +56,25 @@ import net.schueller.peertube.service.VideoPlayerService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
import androidx.fragment.app.FragmentManager; 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_PAUSE;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PLAY; import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PLAY;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP; 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.VideoHelper.canEnterPipMode;
import static net.schueller.peertube.helper.Constants.DEFAULT_THEME;
import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY;
public class VideoPlayActivity extends AppCompatActivity { public class VideoPlayActivity extends AppCompatActivity {
private static final String TAG = "VideoPlayActivity"; private static final String TAG = "VideoPlayActivity";
private static boolean floatMode = false; static boolean floatMode = false;
private static final int REQUEST_CODE = 101; private static final int REQUEST_CODE = 101;
private BroadcastReceiver receiver; 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. //This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode.
@SuppressLint("NewApi") @SuppressLint("NewApi")
public void makePipControls() { public void makePipControls() {
@ -84,7 +83,7 @@ public class VideoPlayActivity extends AppCompatActivity {
ArrayList<RemoteAction> actions = new ArrayList<>(); 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); 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"}) 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); @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); remoteAction = new RemoteAction(icon, "play", "stop the media", pendingIntent);
actions.add(remoteAction); actions.add(remoteAction);
assert videoPlayerFragment != null;
if (videoPlayerFragment.isPaused()) { if (videoPlayerFragment.isPaused()) {
Log.e(TAG, "setting actions with play button"); Log.e(TAG, "setting actions with play button");
actionIntent = new Intent(ACTION_PLAY); actionIntent = new Intent(ACTION_PLAY);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0); pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_play); icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_play);
remoteAction = new RemoteAction(icon, "play", "play the media", pendingIntent); remoteAction = new RemoteAction(icon, "play", "play the media", pendingIntent);
actions.add(remoteAction);
} else { } else {
Log.e(TAG, "setting actions with pause button"); Log.e(TAG, "setting actions with pause button");
actionIntent = new Intent(ACTION_PAUSE); actionIntent = new Intent(ACTION_PAUSE);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0); pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause); icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause);
remoteAction = new RemoteAction(icon, "pause", "pause the media", pendingIntent); remoteAction = new RemoteAction(icon, "pause", "pause the media", pendingIntent);
actions.add(remoteAction);
} }
actions.add(remoteAction);
//add custom actions to pip window //add custom actions to pip window
@ -119,12 +118,13 @@ public class VideoPlayActivity extends AppCompatActivity {
.setActions(actions) .setActions(actions)
.build(); .build();
setPictureInPictureParams(params); setPictureInPictureParams(params);
} }
public void changedToPipMode() { public void changedToPipMode() {
FragmentManager fragmentManager = getSupportFragmentManager(); FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null;
videoPlayerFragment.showControls(false); videoPlayerFragment.showControls(false);
//create custom actions //create custom actions
makePipControls(); makePipControls();
@ -134,11 +134,12 @@ public class VideoPlayActivity extends AppCompatActivity {
filter.addAction(ACTION_STOP); filter.addAction(ACTION_STOP);
filter.addAction(ACTION_PAUSE); filter.addAction(ACTION_PAUSE);
filter.addAction(ACTION_PLAY); filter.addAction(ACTION_PLAY);
filter.addAction((BACKGROUND_AUDIO)); filter.addAction((getString(R.string.app_background_audio)));
receiver = new BroadcastReceiver() { receiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
assert action != null;
if (action.equals(ACTION_PAUSE)) { if (action.equals(ACTION_PAUSE)) {
videoPlayerFragment.pauseVideo(); videoPlayerFragment.pauseVideo();
makePipControls(); makePipControls();
@ -148,7 +149,7 @@ public class VideoPlayActivity extends AppCompatActivity {
makePipControls(); makePipControls();
} }
if (action.equals(BACKGROUND_AUDIO)) { if (action.equals(getString(R.string.app_background_audio))) {
unregisterReceiver(receiver); unregisterReceiver(receiver);
finish(); finish();
} }
@ -164,10 +165,12 @@ public class VideoPlayActivity extends AppCompatActivity {
floatMode = true; floatMode = true;
videoPlayerFragment.showControls(false); videoPlayerFragment.showControls(false);
} }
public void changedToNormalMode() { public void changedToNormalMode() {
FragmentManager fragmentManager = getSupportFragmentManager(); FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null;
videoPlayerFragment.showControls(true); videoPlayerFragment.showControls(true);
if (receiver != null) { if (receiver != null) {
unregisterReceiver(receiver); unregisterReceiver(receiver);
@ -175,6 +178,7 @@ public class VideoPlayActivity extends AppCompatActivity {
Log.v(TAG, "switched to normal"); Log.v(TAG, "switched to normal");
floatMode = false; floatMode = false;
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -182,7 +186,10 @@ public class VideoPlayActivity extends AppCompatActivity {
// Set theme // Set theme
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
setTheme(getResources().getIdentifier( 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", "style",
getPackageName()) getPackageName())
); );
@ -226,7 +233,6 @@ public class VideoPlayActivity extends AppCompatActivity {
assert videoPlayerFragment != null; assert videoPlayerFragment != null;
String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID); String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
Log.v(TAG, "new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.getVideoUuid()); Log.v(TAG, "new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.getVideoUuid());
assert videoPlayerFragment != null;
String playingVideo = videoPlayerFragment.getVideoUuid(); String playingVideo = videoPlayerFragment.getVideoUuid();
if (TextUtils.isEmpty(playingVideo)) { if (TextUtils.isEmpty(playingVideo)) {
@ -245,12 +251,10 @@ public class VideoPlayActivity extends AppCompatActivity {
if (orientation == Configuration.ORIENTATION_LANDSCAPE) { if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
setOrientation(true); setOrientation(true);
} }
} }
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(@NonNull Configuration newConfig) {
Log.v(TAG, "onConfigurationChanged()..."); Log.v(TAG, "onConfigurationChanged()...");
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
@ -263,58 +267,48 @@ public class VideoPlayActivity extends AppCompatActivity {
} }
} }
private void setOrientation(Boolean isLandscape) { private void setOrientation(Boolean isLandscape) {
FragmentManager fragmentManager = getSupportFragmentManager(); FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment); VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
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());
videoPlayerFragment.requireView().setLayoutParams(params);
if (videoMetaFragment != null) {
FragmentTransaction transaction = fragmentManager.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
if (isLandscape) { if (isLandscape) {
assert videoPlayerFragment != null; transaction.hide(videoMetaFragment);
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);
if (videoMetaFragment != null) {
fragmentManager.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
.hide(videoMetaFragment)
.commit();
}
videoPlayerFragment.setIsFullscreen(true);
} else { } else {
assert videoPlayerFragment != null; transaction.show(videoMetaFragment);
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();
}
videoPlayerFragment.setIsFullscreen(isLandscape);
if ( isLandscape ) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null; assert videoPlayerFragment != null;
videoPlayerFragment.destroyVideo(); videoPlayerFragment.destroyVideo();
super.onDestroy(); super.onDestroy();
Log.v(TAG, "onDestroy..."); Log.v(TAG, "onDestroy...");
} }
@ -335,15 +329,6 @@ public class VideoPlayActivity extends AppCompatActivity {
protected void onStop() { protected void onStop() {
super.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) VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
@ -363,24 +348,36 @@ public class VideoPlayActivity extends AppCompatActivity {
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Override @Override
public void onUserLeaveHint() { public void onUserLeaveHint() {
Log.v(TAG, "onUserLeaveHint()...");
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
FragmentManager fragmentManager = getSupportFragmentManager(); FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment); VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
String backgroundBehavior = sharedPref.getString("pref_background_behavior","backgroundStop");
switch(backgroundBehavior){ String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
case "backgroundStop":
assert videoPlayerFragment != null;
assert backgroundBehavior != null;
if ( videoMetaDataFragment.isLeaveAppExpected() )
{
super.onUserLeaveHint();
return;
}
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
Log.v(TAG, "stop the video"); Log.v(TAG, "stop the video");
videoPlayerFragment.pauseVideo(); videoPlayerFragment.pauseVideo();
stopService(new Intent(this, VideoPlayerService.class)); stopService(new Intent(this, VideoPlayerService.class));
super.onBackPressed(); super.onBackPressed();
break;
case "backgroundAudio": } else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
Log.v(TAG, "play the Audio"); Log.v(TAG, "play the Audio");
super.onBackPressed(); super.onBackPressed();
break;
case "backgroundFloat": } else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
Log.v(TAG, "play in floating video"); Log.v(TAG, "play in floating video");
//canEnterPIPMode makes sure API level is high enough //canEnterPIPMode makes sure API level is high enough
if (canEnterPipMode(this)) { if (canEnterPipMode(this)) {
@ -389,44 +386,55 @@ public class VideoPlayActivity extends AppCompatActivity {
} else { } else {
Log.v(TAG, "unable to use pip"); Log.v(TAG, "unable to use pip");
} }
break;
} else {
// Deal with bad entries from older version
Log.v(TAG, "No setting, fallback");
super.onBackPressed();
} }
Log.v(TAG, "onUserLeaveHint()...");
} }
// @RequiresApi(api = Build.VERSION_CODES.O) // @RequiresApi(api = Build.VERSION_CODES.O)
@SuppressLint("NewApi") @SuppressLint("NewApi")
public void onBackPressed() { public void onBackPressed() {
Log.v(TAG, "onBackPressed()...");
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
assert videoPlayerFragment != null;
// copying Youtube behavior to have back button exit full screen. // copying Youtube behavior to have back button exit full screen.
if (videoPlayerFragment.getIsFullscreen()) { if (videoPlayerFragment.getIsFullscreen()) {
Log.v(TAG, "exiting full screen"); Log.v(TAG, "exiting full screen");
videoPlayerFragment.fullScreenToggle(); videoPlayerFragment.fullScreenToggle();
return; return;
} }
// pause video if pref is enabled
if (sharedPref.getBoolean("pref_back_pause", true)) { if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) {
assert videoPlayerFragment != null;
videoPlayerFragment.pauseVideo(); videoPlayerFragment.pauseVideo();
} }
String backgroundBehavior = sharedPref.getString("pref_background_behavior","backgroundStop"); String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
switch (backgroundBehavior){
case "backgroundStop": assert backgroundBehavior != null;
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
Log.v(TAG, "stop the video"); Log.v(TAG, "stop the video");
videoPlayerFragment.pauseVideo(); videoPlayerFragment.pauseVideo();
stopService(new Intent(this, VideoPlayerService.class)); stopService(new Intent(this, VideoPlayerService.class));
super.onBackPressed(); super.onBackPressed();
break;
case "backgroundAudio": } else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
Log.v(TAG, "play the Audio"); Log.v(TAG, "play the Audio");
super.onBackPressed(); super.onBackPressed();
break;
case "backgroundFloat": } else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
Log.v(TAG, "play in floating video"); Log.v(TAG, "play in floating video");
//canEnterPIPMode makes sure API level is high enough //canEnterPIPMode makes sure API level is high enough
if (canEnterPipMode(this)) { if (canEnterPipMode(this)) {
@ -439,22 +447,26 @@ public class VideoPlayActivity extends AppCompatActivity {
Log.v(TAG, "Unable to enter PIP mode"); Log.v(TAG, "Unable to enter PIP mode");
super.onBackPressed(); 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) @RequiresApi(api = Build.VERSION_CODES.O)
public void enterPipMode() { public void enterPipMode() {
Rational rational = new Rational(239, 100); final FragmentManager fragmentManager = getSupportFragmentManager();
Log.v(TAG,rational.toString()); final VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById( R.id.video_player_fragment );
if ( videoPlayerFragment.getVideoAspectRatio() == 0 ) {
Log.i( TAG, "impossible to switch to pip" );
} else {
Rational rational = new Rational( (int) ( videoPlayerFragment.getVideoAspectRatio() * 100 ), 100 );
PictureInPictureParams mParams = PictureInPictureParams mParams =
new PictureInPictureParams.Builder() new PictureInPictureParams.Builder()
.setAspectRatio( rational ) .setAspectRatio( rational )
@ -463,11 +475,15 @@ public class VideoPlayActivity extends AppCompatActivity {
enterPictureInPictureMode( mParams ); enterPictureInPictureMode( mParams );
} }
}
@Override @Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
FragmentManager fragmentManager = getSupportFragmentManager(); FragmentManager fragmentManager = getSupportFragmentManager();
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
if (videoPlayerFragment != null) {
if (isInPictureInPictureMode) { if (isInPictureInPictureMode) {
changedToPipMode(); changedToPipMode();
Log.v(TAG, "switched to pip "); Log.v(TAG, "switched to pip ");
@ -477,6 +493,10 @@ public class VideoPlayActivity extends AppCompatActivity {
Log.v(TAG, "switched to normal"); Log.v(TAG, "switched to normal");
videoPlayerFragment.useController(true); videoPlayerFragment.useController(true);
} }
} else {
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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.adapter; package net.schueller.peertube.adapter;
@ -160,7 +159,7 @@ public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.AccountV
AccountViewHolder(View itemView) { AccountViewHolder(View itemView) {
super(itemView); super(itemView);
name = itemView.findViewById(R.id.name); name = itemView.findViewById(R.id.sl_row_name);
thumb = itemView.findViewById(R.id.thumb); thumb = itemView.findViewById(R.id.thumb);
avatar = itemView.findViewById(R.id.avatar); avatar = itemView.findViewById(R.id.avatar);
videoMeta = itemView.findViewById(R.id.videoMeta); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.adapter; package net.schueller.peertube.adapter;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import net.schueller.peertube.R; 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.helper.APIUrlHelper;
import net.schueller.peertube.model.Server; import net.schueller.peertube.model.Server;
@ -38,17 +35,19 @@ import java.util.ArrayList;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import static android.app.Activity.RESULT_OK; 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 ArrayList<Server> serverList;
private SelectServerActivity activity; private SearchServerActivity activity;
private String baseUrl; private String baseUrl;
public ServerAdapter(ArrayList<Server> serverList, SelectServerActivity activity) { public ServerSearchAdapter(ArrayList<Server> serverList, SearchServerActivity activity) {
this.serverList = serverList; this.serverList = serverList;
this.activity = activity; this.activity = activity;
} }
@ -57,7 +56,7 @@ public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.AccountVie
@Override @Override
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); 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); 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_yes :
R.string.server_selection_signup_allowed_no 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 -> { holder.itemView.setOnClickListener(v -> {
// SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity);
// SharedPreferences.Editor editor = sharedPref.edit();
String serverUrl = APIUrlHelper.cleanServerUrl(serverList.get(position).getHost()); 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(); Toast.makeText(activity, activity.getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show();
Intent intent = new Intent(); Intent intent = new Intent();
@ -138,17 +155,19 @@ public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.AccountVie
return serverList.size(); 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) { AccountViewHolder(View itemView) {
super(itemView); super(itemView);
name = itemView.findViewById(R.id.name); name = itemView.findViewById(R.id.sl_row_name);
host = itemView.findViewById(R.id.host); host = itemView.findViewById(R.id.sl_row_host);
signupAllowed = itemView.findViewById(R.id.signupAllowed); signupAllowed = itemView.findViewById(R.id.sl_row_signup_allowed);
shortDescription = itemView.findViewById(R.id.shortDescription); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.adapter; package net.schueller.peertube.adapter;
@ -63,7 +62,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
@Override @Override
public VideoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public VideoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); 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); baseUrl = APIUrlHelper.getUrl(context);
@ -176,7 +175,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
VideoViewHolder(View itemView) { VideoViewHolder(View itemView) {
super(itemView); super(itemView);
name = itemView.findViewById(R.id.name); name = itemView.findViewById(R.id.sl_row_name);
thumb = itemView.findViewById(R.id.thumb); thumb = itemView.findViewById(R.id.thumb);
avatar = itemView.findViewById(R.id.avatar); avatar = itemView.findViewById(R.id.avatar);
videoMeta = itemView.findViewById(R.id.videoMeta); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.application; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.database; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.database; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.fragment; package net.schueller.peertube.fragment;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@ -44,7 +44,7 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
public static final String TAG = "VideoMenuQuality"; public static final String TAG = "VideoMenuQuality";
private static File autoQualityFile; private static File autoQualityFile;
public static VideoMenuQualityFragment newInstance(ArrayList<File> files) { public static VideoMenuQualityFragment newInstance(Context context, ArrayList<File> files) {
mFiles = files; mFiles = files;
@ -53,7 +53,7 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
autoQualityFile = new File(); autoQualityFile = new File();
Resolution autoQualityResolution = new Resolution(); Resolution autoQualityResolution = new Resolution();
autoQualityResolution.setId(0); autoQualityResolution.setId(0);
autoQualityResolution.setLabel("Auto"); autoQualityResolution.setLabel(context.getString(R.string.menu_video_options_quality_automated));
autoQualityFile.setId(0); autoQualityFile.setId(0);
autoQualityFile.setResolution(autoQualityResolution); autoQualityFile.setResolution(autoQualityResolution);
} }
@ -74,11 +74,11 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
false); false);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getContext()); 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) { 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); TextView iconView = menuRow.findViewById(R.id.video_quality_icon);
iconView.setId(file.getResolution().getId()); iconView.setId(file.getResolution().getId());
@ -90,7 +90,7 @@ public class VideoMenuQualityFragment extends BottomSheetDialogFragment {
textView.setOnClickListener(view1 -> { textView.setOnClickListener(view1 -> {
// Log.v(TAG, file.getResolution().getLabel()); // Log.v(TAG, file.getResolution().getLabel());
SharedPreferences.Editor editor = sharedPref.edit(); 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(); editor.apply();
for (File fileV : mFiles) { 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.fragment; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.fragment; package net.schueller.peertube.fragment;
@ -39,6 +38,7 @@ import com.squareup.picasso.Picasso;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.helper.MetaDataHelper; import net.schueller.peertube.helper.MetaDataHelper;
import net.schueller.peertube.intents.Intents; import net.schueller.peertube.intents.Intents;
import net.schueller.peertube.model.Account; 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 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 Rating videoRating;
private ColorStateList defaultTextColor; private ColorStateList defaultTextColor;
private boolean leaveAppExpected = false;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
// Inflate the layout for this fragment // Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_video_meta, container, false); 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(); Context context = getContext();
Activity activity = getActivity(); Activity activity = getActivity();
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); 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 // Thumbs up
Button thumbsUpButton = activity.findViewById(R.id.video_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); thumbsUpButton.setText(R.string.video_thumbs_up_icon);
new Iconics.IconicsBuilder().ctx(context).on(thumbsUpButton).build(); new Iconics.IconicsBuilder().ctx(context).on(thumbsUpButton).build();
thumbsUpButton.setOnClickListener(v -> { 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 // Thumbs Down
Button thumbsDownButton = activity.findViewById(R.id.video_thumbs_down); Button thumbsDownButton = activity.findViewById(R.id.video_thumbs_down);
thumbsDownButton.setText(R.string.video_thumbs_down_icon); thumbsDownButton.setText(R.string.video_thumbs_down_icon);
new Iconics.IconicsBuilder().ctx(context).on(thumbsDownButton).build(); new Iconics.IconicsBuilder().ctx(context).on(thumbsDownButton).build();
thumbsDownButton.setOnClickListener(v -> { thumbsDownButton.setOnClickListener(v -> {
rateVideo(false, video.getId()); rateVideo(false, video);
}); });
// video rating // video rating
videoRating = new Rating(); videoRating = new Rating();
videoRating.setRating("none"); // default videoRating.setRating(RATING_NONE); // default
updateVideoRating(); updateVideoRating(video);
// Retrieve which rating the user gave to this video
if (Session.getInstance().isLoggedIn()) { if (Session.getInstance().isLoggedIn()) {
Call<Rating> call = videoDataService.getVideoRating(video.getId()); Call<Rating> call = videoDataService.getVideoRating(video.getId());
call.enqueue(new Callback<Rating>() { call.enqueue(new Callback<Rating>() {
@ -118,25 +131,26 @@ public class VideoMetaDataFragment extends Fragment {
@Override @Override
public void onResponse(Call<Rating> call, Response<Rating> response) { public void onResponse(Call<Rating> call, Response<Rating> response) {
videoRating = response.body(); videoRating = response.body();
updateVideoRating(); updateVideoRating(video);
} }
@Override @Override
public void onFailure(Call<Rating> call, Throwable t) { 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 // Share
Button videoShareButton = activity.findViewById(R.id.video_share); Button videoShareButton = activity.findViewById(R.id.video_share);
videoShareButton.setText(R.string.video_share_icon); videoShareButton.setText(R.string.video_share_icon);
new Iconics.IconicsBuilder().ctx(context).on(videoShareButton).build(); 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 // Download
Button videoDownloadButton = activity.findViewById(R.id.video_download); Button videoDownloadButton = activity.findViewById(R.id.video_download);
@ -145,6 +159,7 @@ public class VideoMetaDataFragment extends Fragment {
videoDownloadButton.setOnClickListener(v -> { videoDownloadButton.setOnClickListener(v -> {
// get permission to store file // get permission to store file
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 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); ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Intents.Download(context, video); Intents.Download(context, video);
@ -156,7 +171,6 @@ public class VideoMetaDataFragment extends Fragment {
} }
}); });
Account account = video.getAccount(); Account account = video.getAccount();
// owner / creator Avatar // owner / creator Avatar
@ -172,7 +186,7 @@ public class VideoMetaDataFragment extends Fragment {
// title / name // title / name
TextView videoName = activity.findViewById(R.id.name); TextView videoName = activity.findViewById(R.id.sl_row_name);
videoName.setText(video.getName()); videoName.setText(video.getName());
// created at / views // created at / views
@ -198,7 +212,6 @@ public class VideoMetaDataFragment extends Fragment {
TextView videoDescription = activity.findViewById(R.id.description); TextView videoDescription = activity.findViewById(R.id.description);
videoDescription.setText(video.getDescription()); videoDescription.setText(video.getDescription());
// video privacy // video privacy
TextView videoPrivacy = activity.findViewById(R.id.video_privacy); TextView videoPrivacy = activity.findViewById(R.id.video_privacy);
videoPrivacy.setText(video.getPrivacy().getLabel()); videoPrivacy.setText(video.getPrivacy().getLabel());
@ -211,7 +224,7 @@ public class VideoMetaDataFragment extends Fragment {
TextView videoLicense = activity.findViewById(R.id.video_license); TextView videoLicense = activity.findViewById(R.id.video_license);
videoLicense.setText(video.getLicence().getLabel()); videoLicense.setText(video.getLicence().getLabel());
// video langauge // video language
TextView videoLanguage = activity.findViewById(R.id.video_language); TextView videoLanguage = activity.findViewById(R.id.video_language);
videoLanguage.setText(video.getLanguage().getLabel()); videoLanguage.setText(video.getLanguage().getLabel());
@ -219,7 +232,6 @@ public class VideoMetaDataFragment extends Fragment {
TextView videoTags = activity.findViewById(R.id.video_tags); TextView videoTags = activity.findViewById(R.id.video_tags);
videoTags.setText(android.text.TextUtils.join(", ", video.getTags())); videoTags.setText(android.text.TextUtils.join(", ", video.getTags()));
// more button // more button
TextView moreButton = activity.findViewById(R.id.moreButton); TextView moreButton = activity.findViewById(R.id.moreButton);
moreButton.setText(R.string.video_more_icon); moreButton.setText(R.string.video_more_icon);
@ -259,8 +271,7 @@ public class VideoMetaDataFragment extends Fragment {
} }
void updateVideoRating(Video video) {
void updateVideoRating() {
Button thumbsUpButton = getActivity().findViewById(R.id.video_thumbs_up); Button thumbsUpButton = getActivity().findViewById(R.id.video_thumbs_up);
Button thumbsDownButton = getActivity().findViewById(R.id.video_thumbs_down); 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}); TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorPrimary});
int accentColor = a.getColor(0, 0); int accentColor = a.getColor(0, 0);
if (videoRating.getRating().equals(getString(R.string.video_rating_none))) { // Change the color of the thumbs
switch (videoRating.getRating()) {
case RATING_NONE:
thumbsUpButton.setTextColor(defaultTextColor); thumbsUpButton.setTextColor(defaultTextColor);
thumbsDownButton.setTextColor(defaultTextColor); thumbsDownButton.setTextColor(defaultTextColor);
//Log.v(TAG, getString(R.string.video_rating_none)); break;
case RATING_LIKE:
} else if (videoRating.getRating().equals(getString(R.string.video_rating_like))) {
thumbsUpButton.setTextColor(accentColor); thumbsUpButton.setTextColor(accentColor);
thumbsDownButton.setTextColor(defaultTextColor); thumbsDownButton.setTextColor(defaultTextColor);
//Log.v(TAG, getString(R.string.video_rating_like)); break;
case RATING_DISLIKE:
} else if (videoRating.getRating().equals(getString(R.string.video_rating_dislike))) {
thumbsUpButton.setTextColor(defaultTextColor); thumbsUpButton.setTextColor(defaultTextColor);
thumbsDownButton.setTextColor(accentColor); thumbsDownButton.setTextColor(accentColor);
//Log.v(TAG, getString(R.string.video_rating_dislike)); 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(); a.recycle();
} }
void rateVideo(Boolean rate, Integer videoId) { void rateVideo(Boolean like, Video video) {
// TODO cleanup
if (Session.getInstance().isLoggedIn()) { if (Session.getInstance().isLoggedIn()) {
final String ratePayload;
String ratePayload = getString(R.string.video_rating_none); switch (videoRating.getRating()) {
case RATING_LIKE:
if (rate) { ratePayload = like ? RATING_NONE : RATING_DISLIKE;
// thumbsup break;
if (videoRating.getRating().equals(getString(R.string.video_rating_none))) { case RATING_DISLIKE:
ratePayload = getString(R.string.video_rating_like); ratePayload = like ? RATING_LIKE : RATING_NONE;
} break;
} else { case RATING_NONE:
// thumbsdown default:
if (videoRating.getRating().equals(getString(R.string.video_rating_none))) { ratePayload = like ? RATING_LIKE : RATING_DISLIKE;
ratePayload = getString(R.string.video_rating_dislike); break;
}
} }
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"), "{\"rating\":\"" + ratePayload + "\"}"); RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"), "{\"rating\":\"" + ratePayload + "\"}");
String apiBaseURL = APIUrlHelper.getUrlWithVersion(getContext()); 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); Call<ResponseBody> call = videoDataService.rateVideo(video.getId(), body);
final String newRating = ratePayload;
call.enqueue(new Callback<ResponseBody>() { call.enqueue(new Callback<ResponseBody>() {
@Override @Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//Log.v(TAG, response.toString()); //Log.v(TAG, response.toString());
// if 20x update likes // if 20x, update likes/dislikes
if (response.isSuccessful()) { if (response.isSuccessful()) {
videoRating.setRating(newRating); String previousRating = videoRating.getRating();
updateVideoRating();
// 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 { } else {
Toast.makeText(getContext(), getString(R.string.video_login_required_for_service), Toast.LENGTH_SHORT).show(); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.fragment; package net.schueller.peertube.fragment;
import android.annotation.SuppressLint; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -28,6 +27,7 @@ import android.widget.TextView;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.mikepenz.iconics.Iconics; import com.mikepenz.iconics.Iconics;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.model.File; import net.schueller.peertube.model.File;
import net.schueller.peertube.service.VideoPlayerService; import net.schueller.peertube.service.VideoPlayerService;
@ -35,6 +35,7 @@ import net.schueller.peertube.service.VideoPlayerService;
import java.util.ArrayList; import java.util.ArrayList;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
public class VideoOptionsFragment extends BottomSheetDialogFragment { public class VideoOptionsFragment extends BottomSheetDialogFragment {
@ -62,32 +63,41 @@ public class VideoOptionsFragment extends BottomSheetDialogFragment {
LinearLayout menuHolder = view.findViewById(R.id.video_options_popup); LinearLayout menuHolder = view.findViewById(R.id.video_options_popup);
// Video Speed // 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 iconView = menuRow.findViewById(R.id.video_quality_icon);
TextView textView = menuRow.findViewById(R.id.video_quality_text); 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); iconView.setText(R.string.video_option_speed_icon);
new Iconics.IconicsBuilder().ctx(getContext()).on(iconView).build(); new Iconics.IconicsBuilder().ctx(getContext()).on(iconView).build();
textView.setOnClickListener(view1 -> { textView.setOnClickListener(view1 -> {
VideoMenuSpeedFragment videoMenuSpeedFragment = VideoMenuSpeedFragment videoMenuSpeedFragment =
VideoMenuSpeedFragment.newInstance(videoPlayerService); VideoMenuSpeedFragment.newInstance(videoPlayerService);
videoMenuSpeedFragment.show(getActivity().getSupportFragmentManager(), videoMenuSpeedFragment.show(requireActivity().getSupportFragmentManager(),
VideoMenuSpeedFragment.TAG); VideoMenuSpeedFragment.TAG);
}); });
menuHolder.addView(menuRow); menuHolder.addView(menuRow);
// Video Quality // 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 iconView2 = menuRow2.findViewById(R.id.video_quality_icon);
TextView textView2 = menuRow2.findViewById(R.id.video_quality_text); 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); iconView2.setText(R.string.video_option_quality_icon);
new Iconics.IconicsBuilder().ctx(getContext()).on(iconView2).build(); new Iconics.IconicsBuilder().ctx(getContext()).on(iconView2).build();
textView2.setOnClickListener(view1 -> { textView2.setOnClickListener(view1 -> {
VideoMenuQualityFragment videoMenuQualityFragment = VideoMenuQualityFragment videoMenuQualityFragment =
VideoMenuQualityFragment.newInstance(files); VideoMenuQualityFragment.newInstance(getContext(), files);
videoMenuQualityFragment.show(getActivity().getSupportFragmentManager(), videoMenuQualityFragment.show(requireActivity().getSupportFragmentManager(),
videoMenuQualityFragment.TAG); VideoMenuQualityFragment.TAG);
}); });
menuHolder.addView(menuRow2); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.fragment; package net.schueller.peertube.fragment;
import android.app.Activity; import android.app.Activity;
import android.app.AppOpsManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -60,21 +58,23 @@ import com.mikepenz.iconics.Iconics;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.ErrorHelper;
import net.schueller.peertube.model.File; import net.schueller.peertube.model.File;
import net.schueller.peertube.model.Video; import net.schueller.peertube.model.Video;
import net.schueller.peertube.network.GetVideoDataService; import net.schueller.peertube.network.GetVideoDataService;
import net.schueller.peertube.network.RetrofitInstance; import net.schueller.peertube.network.RetrofitInstance;
import net.schueller.peertube.service.VideoPlayerService; import net.schueller.peertube.service.VideoPlayerService;
import java.util.Objects;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode;
public class VideoPlayerFragment extends Fragment implements VideoRendererEventListener { public class VideoPlayerFragment extends Fragment implements VideoRendererEventListener {
private String mVideoUuid; private String mVideoUuid;
@ -86,6 +86,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
private VideoPlayerService mService; private VideoPlayerService mService;
private TorrentStream torrentStream; private TorrentStream torrentStream;
private LinearLayout torrentStatus; private LinearLayout torrentStatus;
private float aspectRatio;
private static final String TAG = "VideoPlayerFragment"; private static final String TAG = "VideoPlayerFragment";
private GestureDetector mDetector; private GestureDetector mDetector;
@ -112,6 +113,14 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
mBound = false; mBound = false;
} }
}; };
private AspectRatioFrameLayout.AspectRatioListener aspectRatioListerner = new AspectRatioFrameLayout.AspectRatioListener()
{
@Override
public void onAspectRatioUpdated( float targetAspectRatio, float naturalAspectRatio, boolean aspectRatioMismatch )
{
aspectRatio = targetAspectRatio;
}
};
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 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 = activity.findViewById(R.id.torrent_progress);
progressBar.setMax(100); progressBar.setMax(100);
assert context != null;
simpleExoPlayerView = new PlayerView(context); simpleExoPlayerView = new PlayerView(context);
simpleExoPlayerView = activity.findViewById(R.id.video_view); simpleExoPlayerView = activity.findViewById(R.id.video_view);
@ -143,6 +153,8 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
mDetector = new GestureDetector(context, new MyGestureListener()); mDetector = new GestureDetector(context, new MyGestureListener());
simpleExoPlayerView.setOnTouchListener(touchListener); simpleExoPlayerView.setOnTouchListener(touchListener);
simpleExoPlayerView.setAspectRatioListener( aspectRatioListerner );
torrentStatus = activity.findViewById(R.id.exo_torrent_status); torrentStatus = activity.findViewById(R.id.exo_torrent_status);
// Full screen Icon // Full screen Icon
@ -171,7 +183,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
// get video details from api // get video details from api
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); 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); Call<Video> call = service.getVideoData(mVideoUuid);
@ -195,30 +207,31 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
@Override @Override
public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) {
Log.wtf(TAG, t.fillInStackTrace()); 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) { public void useController(boolean value) {
if (mBound) { if (mBound) {
simpleExoPlayerView.setUseController(value); simpleExoPlayerView.setUseController(value);
} }
} }
private void playVideo(Video video) { private void playVideo(Video video) {
Context context = getContext(); Context context = getContext();
// video Meta fragment // video Meta fragment
assert getFragmentManager() != null;
VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment) VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment)
getFragmentManager().findFragmentById(R.id.video_meta_data_fragment); requireActivity().getSupportFragmentManager().findFragmentById(R.id.video_meta_data_fragment);
assert videoMetaDataFragment != null; assert videoMetaDataFragment != null;
videoMetaDataFragment.updateVideoMeta(video, mService); videoMetaDataFragment.updateVideoMeta(video, mService);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); 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); torrentStatus.setVisibility(View.VISIBLE);
String stream = video.getFiles().get(0).getTorrentUrl(); String stream = video.getFiles().get(0).getTorrentUrl();
Log.v(TAG, "getTorrentUrl : " + video.getFiles().get(0).getTorrentUrl()); Log.v(TAG, "getTorrentUrl : " + video.getFiles().get(0).getTorrentUrl());
@ -226,9 +239,11 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
torrentStream.startStream(stream); torrentStream.startStream(stream);
} else { } else {
Integer videoQuality = sharedPref.getInt("pref_quality", 0); Integer videoQuality = sharedPref.getInt(getString(R.string.pref_quality_key), 0);
//get video qualities //get video qualities
/// #
if (video.getFiles().size() > 0) {
String urlToPlay = video.getFiles().get( 0 ).getFileUrl(); String urlToPlay = video.getFiles().get( 0 ).getFileUrl();
for ( File file : video.getFiles() ) { for ( File file : video.getFiles() ) {
// Set quality if it matches // Set quality if it matches
@ -237,9 +252,12 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
} }
} }
mService.setCurrentStreamUrl( urlToPlay ); mService.setCurrentStreamUrl( urlToPlay );
torrentStatus.setVisibility(View.GONE); torrentStatus.setVisibility(View.GONE);
startPlayer(); startPlayer();
} else {
stopVideo();
Toast.makeText(context, R.string.api_error, Toast.LENGTH_LONG).show();
}
} }
Log.v(TAG, "end of load Video"); Log.v(TAG, "end of load Video");
@ -247,7 +265,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
} }
private void startPlayer() { private void startPlayer() {
Util.startForegroundService(Objects.requireNonNull(getContext()), videoPlayerIntent); Util.startForegroundService(requireContext(), videoPlayerIntent);
} }
@ -263,26 +281,33 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
mService.player.setPlayWhenReady(false); mService.player.setPlayWhenReady(false);
} }
} }
public void pauseToggle() { public void pauseToggle() {
if (mBound) { if (mBound) {
mService.player.setPlayWhenReady(!mService.player.getPlayWhenReady()); mService.player.setPlayWhenReady(!mService.player.getPlayWhenReady());
} }
} }
public void unPauseVideo() { public void unPauseVideo() {
if (mBound) { if (mBound) {
mService.player.setPlayWhenReady(true); mService.player.setPlayWhenReady(true);
} }
} }
public float getVideoAspectRatio() { return aspectRatio; }
public boolean isPaused() { public boolean isPaused() {
return !mService.player.getPlayWhenReady(); return !mService.player.getPlayWhenReady();
} }
public void showControls(boolean value) { public void showControls(boolean value) {
simpleExoPlayerView.setUseController(value); simpleExoPlayerView.setUseController(value);
} }
public void stopVideo() { public void stopVideo() {
if (mBound) { if (mBound) {
Objects.requireNonNull(getContext()).unbindService(mConnection); requireContext().unbindService(mConnection);
mBound = false; mBound = false;
} }
} }
@ -290,7 +315,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
public void setIsFullscreen(Boolean fullscreen) { public void setIsFullscreen(Boolean fullscreen) {
isFullscreen = fullscreen; isFullscreen = fullscreen;
TextView fullscreenButton = getActivity().findViewById(R.id.exo_fullscreen); TextView fullscreenButton = requireActivity().findViewById(R.id.exo_fullscreen);
if (fullscreen) { if (fullscreen) {
fullscreenButton.setText(R.string.video_compress_icon); fullscreenButton.setText(R.string.video_compress_icon);
} else { } else {
@ -302,15 +327,17 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
public Boolean getIsFullscreen() { public Boolean getIsFullscreen() {
return isFullscreen; return isFullscreen;
} }
public void fullScreenToggle() { public void fullScreenToggle() {
if (!isFullscreen) { if (!isFullscreen) {
setIsFullscreen(true); setIsFullscreen(true);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else { } else {
setIsFullscreen(false); setIsFullscreen(false);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} }
} }
/** /**
* Torrent Playback * Torrent Playback
* *
@ -404,18 +431,6 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
Log.v(TAG, "onVideoDisabled()..."); 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() { View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
@ -423,9 +438,11 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
} }
}; };
public String getVideoUuid() { public String getVideoUuid() {
return mVideoUuid; return mVideoUuid;
} }
class MyGestureListener extends GestureDetector.SimpleOnGestureListener { class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
/* /*
@Override @Override
@ -471,7 +488,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL
if (velocityY > 4000) { if (velocityY > 4000) {
Log.d(TAG, "we have a drag down event"); Log.d(TAG, "we have a drag down event");
if (canEnterPipMode(getContext())) { if (canEnterPipMode(getContext())) {
getActivity().enterPictureInPictureMode(); requireActivity().enterPictureInPictureMode();
} }
} }
if ((velocityX > 2000) && (Math.abs(velocityY) < 2000)) { if ((velocityX > 2000) && (Math.abs(velocityY) < 2000)) {

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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.helper; package net.schueller.peertube.helper;
@ -32,7 +31,7 @@ public class APIUrlHelper{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
// validate URL is valid // 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)) { if (!URLUtil.isValidUrl(URL)) {
return "http://invalid"; return "http://invalid";
} }
@ -43,6 +42,11 @@ public class APIUrlHelper{
return APIUrlHelper.getUrl(context) + "/api/v1/"; 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) { public static String getShareUrl(Context context, String videoUuid) {
return APIUrlHelper.getUrl(context) + "/videos/watch/" + 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.helper; package net.schueller.peertube.helper;
import android.content.Context; import android.content.Context;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import java.time.Duration; import org.ocpsoft.prettytime.PrettyTime;
import java.time.Period;
import java.util.Date; import java.util.Date;
import java.util.Locale;
public class MetaDataHelper { public class MetaDataHelper {
public static String getMetaString(Date getCreatedAt, Integer viewCount, Context context) { 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) + context.getResources().getString(R.string.meta_data_seperator) +
viewCount + context.getResources().getString(R.string.meta_data_views)); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.intents; package net.schueller.peertube.intents;
@ -27,6 +26,7 @@ import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.webkit.URLUtil; import android.webkit.URLUtil;
import android.widget.Toast;
import com.github.se_bastiaan.torrentstream.TorrentOptions; import com.github.se_bastiaan.torrentstream.TorrentOptions;
@ -40,6 +40,7 @@ import androidx.core.app.ActivityCompat;
public class Intents { public class Intents {
private static final String TAG = "Intents";
/** /**
* https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c * https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c
@ -68,6 +69,8 @@ public class Intents {
// TODO, offer which version to download // TODO, offer which version to download
public static void Download(Context context, Video video) { public static void Download(Context context, Video video) {
if (video.getFiles().size() > 0)
{
String url = video.getFiles().get( 0 ).getFileDownloadUrl(); String url = video.getFiles().get( 0 ).getFileDownloadUrl();
// make sure it is a valid filename // make sure it is a valid filename
String destFilename = video.getName().replaceAll( "[^a-zA-Z0-9]", "_" ) + "." + MimeTypeMap.getFileExtensionFromUrl( URLUtil.guessFileName( url, null, null ) ); String destFilename = video.getName().replaceAll( "[^a-zA-Z0-9]", "_" ) + "." + MimeTypeMap.getFileExtensionFromUrl( URLUtil.guessFileName( url, null, null ) );
@ -83,5 +86,8 @@ public class Intents {
// get download service and enqueue file // get download service and enqueue file
DownloadManager manager = (DownloadManager) context.getSystemService( Context.DOWNLOAD_SERVICE ); DownloadManager manager = (DownloadManager) context.getSystemService( Context.DOWNLOAD_SERVICE );
manager.enqueue( request ); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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; package net.schueller.peertube.model;
public class Me { 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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; package net.schueller.peertube.model;
import com.google.gson.annotations.SerializedName; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; package net.schueller.peertube.model;
import java.util.ArrayList;
import java.util.Date;
public class Server { public class Server {
private Integer id; private Integer id;
@ -26,16 +28,20 @@ public class Server {
private String version; private String version;
private Boolean signupAllowed; private Boolean signupAllowed;
private Double userVideoQuota; private Double userVideoQuota;
private Category category;
private ArrayList<String> languages;
private Boolean autoBlacklistUserVideosEnabled;
private String defaultNSFWPolicy;
private Boolean isNSFW;
private Integer totalUsers; private Integer totalUsers;
private Integer totalVideos; private Integer totalVideos;
private Integer totalLocalVideos; private Integer totalLocalVideos;
private Integer totalInstanceFollowers; private Integer totalInstanceFollowers;
private Integer totalInstanceFollowing; private Integer totalInstanceFollowing;
private Boolean supportsIPv6; private Boolean supportsIPv6;
private String country; private String country;
private Integer health; private Integer health;
private Date createdAt;
public Integer getId() { public Integer getId() {
return id; return id;
@ -93,6 +99,46 @@ public class Server {
this.userVideoQuota = userVideoQuota; 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() { public Integer getTotalUsers() {
return totalUsers; return totalUsers;
} }
@ -156,4 +202,12 @@ public class Server {
public void setHealth(Integer health) { public void setHealth(Integer health) {
this.health = 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.model; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.network; package net.schueller.peertube.network;
@ -42,4 +41,14 @@ public interface AuthenticationService {
@Field("password") String password @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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.network; package net.schueller.peertube.network;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import java.io.IOException; import java.io.IOException;
import okhttp3.Interceptor; import okhttp3.Interceptor;
@ -31,8 +32,7 @@ public class AuthorizationInterceptor implements Interceptor {
public AuthorizationInterceptor() { public AuthorizationInterceptor() {
} }
@NonNull
@Override @Override
public Response intercept(Chain chain) throws IOException { 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.network; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.network; package net.schueller.peertube.network;
@ -27,7 +26,8 @@ public interface GetServerListDataService {
@GET("instances/") @GET("instances/")
Call<ServerList> getInstancesData( Call<ServerList> getInstancesData(
@Query("start") int start, @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; package net.schueller.peertube.network;
import net.schueller.peertube.model.Account; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.network; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.network; 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 okhttp3.OkHttpClient;
import retrofit2.Retrofit; import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.gson.GsonConverterFactory;
@ -26,13 +30,20 @@ public class RetrofitInstance {
private static Retrofit retrofit; private static Retrofit retrofit;
private static String baseUrl; private static String baseUrl;
public static Retrofit getRetrofitInstance(String newBaseUrl) { public static Retrofit getRetrofitInstance(String newBaseUrl, boolean insecure) {
if (retrofit == null || !newBaseUrl.equals(baseUrl)) { if (retrofit == null || !newBaseUrl.equals(baseUrl)) {
baseUrl = newBaseUrl; 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.addInterceptor(new AuthorizationInterceptor());
okhttpClientBuilder.authenticator(new AccessTokenAuthenticator());
retrofit = new retrofit2.Retrofit.Builder() retrofit = new retrofit2.Retrofit.Builder()
.client(okhttpClientBuilder.build()) .client(okhttpClientBuilder.build())

View File

@ -1,30 +1,30 @@
/* /*
* 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.network; package net.schueller.peertube.network;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.application.AppApplication; import net.schueller.peertube.application.AppApplication;
import static net.schueller.peertube.service.LoginService.refreshToken;
public class Session { public class Session {
private static volatile Session sSoleInstance; private static volatile Session sSoleInstance;
@ -58,7 +58,6 @@ public class Session {
} }
public boolean isLoggedIn() { public boolean isLoggedIn() {
// check if token exist or not // check if token exist or not
// return true if exist otherwise false // return true if exist otherwise false
@ -69,13 +68,6 @@ public class Session {
return getToken() != null; 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() { public String getToken() {
// return the token that was saved earlier // return the token that was saved earlier
@ -89,27 +81,24 @@ public class Session {
return null; 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() { public String getPassword() {
return sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_auth_password), null); 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() { public void invalidate() {
// get called when user become logged out // get called when user become logged out
// delete token and other user info // 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_password), null);
editor.putString(context.getString(R.string.pref_auth_username), 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_access), null);
editor.putString(context.getString(R.string.pref_token_refresh), null);
editor.commit(); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.provider; 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.service; package net.schueller.peertube.service;
@ -26,6 +25,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.application.AppApplication;
import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.model.OauthClient; import net.schueller.peertube.model.OauthClient;
import net.schueller.peertube.model.Token; import net.schueller.peertube.model.Token;
@ -42,15 +42,14 @@ public class LoginService {
private static final String TAG = "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(); Context context = getContext();
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(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(); Call<OauthClient> call = service.getOauthClientLocal();
@ -62,6 +61,14 @@ public class LoginService {
OauthClient oauthClient = response.body(); 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( Call<Token> call2 = service.getAuthenticationToken(
oauthClient.getClientId(), oauthClient.getClientId(),
oauthClient.getClientSecret(), oauthClient.getClientSecret(),
@ -79,13 +86,9 @@ public class LoginService {
Token token = response2.body(); Token token = response2.body();
SharedPreferences.Editor editor = sharedPref.edit(); assert token != null;
// TODO: calc expiration
//editor.putInt(getString(R.string.pref_token_expiration), token.getRefreshToken());
editor.putString(context.getString(R.string.pref_token_access), token.getAccessToken()); 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.putString(context.getString(R.string.pref_token_type), token.getTokenType());
editor.apply(); 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 * 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 * it under the terms of the GNU Affero General Public License as
* the Free Software Foundation, either version 3 of the License, or * published by the Free Software Foundation, either version 3 of the
* (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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 * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package net.schueller.peertube.service; package net.schueller.peertube.service;
@ -33,6 +32,7 @@ import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.webkit.URLUtil;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.v4.media.MediaDescriptionCompat; 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.audio.AudioAttributes;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator; 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.source.ExtractorMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.ui.PlayerNotificationManager; 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.R;
import net.schueller.peertube.activity.VideoPlayActivity; import net.schueller.peertube.activity.VideoPlayActivity;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.MetaDataHelper; import net.schueller.peertube.helper.MetaDataHelper;
import net.schueller.peertube.model.Video; import net.schueller.peertube.model.Video;
import okhttp3.OkHttpClient;
import static android.media.session.PlaybackState.ACTION_PAUSE; import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY; import static android.media.session.PlaybackState.ACTION_PLAY;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP; import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
import static net.schueller.peertube.activity.VideoListActivity.EXTRA_VIDEOID; import static net.schueller.peertube.activity.VideoListActivity.EXTRA_VIDEOID;
import static net.schueller.peertube.network.UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder;
public class VideoPlayerService extends Service { public class VideoPlayerService extends Service {
private static final String TAG = "VideoPlayerService"; private static final String TAG = "VideoPlayerService";
private static final String MEDIA_SESSION_TAG = "peertube_player"; private static final String MEDIA_SESSION_TAG = "peertube_player";
private final IBinder mBinder = new LocalBinder(); private final IBinder mBinder = new LocalBinder();
private static final String PLAYBACK_CHANNEL_ID = "playback_channel"; private static final String PLAYBACK_CHANNEL_ID = "playback_channel";
private static final Integer PLAYBACK_NOTIFICATION_ID = 1; private static final Integer PLAYBACK_NOTIFICATION_ID = 1;
public SimpleExoPlayer player; public SimpleExoPlayer player;
private Video currentVideo; private Video currentVideo;
private String currentStreamUrl; private String currentStreamUrl;
private PlayerNotificationManager playerNotificationManager; private PlayerNotificationManager playerNotificationManager;
private IntentFilter becomeNoisyIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); private IntentFilter becomeNoisyIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver(); private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver();
@Override @Override
@ -105,10 +114,10 @@ public class VideoPlayerService extends Service {
registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter); 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); Log.v(TAG, "ACTION_PAUSE: " + playbackState);
unregisterReceiver(myNoisyAudioStreamReceiver); safeUnregisterReceiver();
myNoisyAudioStreamReceiver=null;
} }
} }
}); });
@ -131,14 +140,9 @@ public class VideoPlayerService extends Service {
if (playerNotificationManager != null) { if (playerNotificationManager != null) {
playerNotificationManager.setPlayer(null); playerNotificationManager.setPlayer(null);
} }
//Was seeing an error when exiting the program about about not unregistering the receiver. //Was seeing an error when exiting the program about not unregistering the receiver.
try { safeUnregisterReceiver();
if (null!=myNoisyAudioStreamReceiver) {
this.unregisterReceiver(myNoisyAudioStreamReceiver);
}
} catch (Exception e) {
Log.e("VideoPlayerService", "attempted to unregister a nonregistered service");
}
if (player != null) { if (player != null) {
player.release(); player.release();
player = null; player = null;
@ -146,6 +150,15 @@ public class VideoPlayerService extends Service {
super.onDestroy(); super.onDestroy();
} }
private void safeUnregisterReceiver()
{
try {
unregisterReceiver(myNoisyAudioStreamReceiver);
} catch (Exception e) {
Log.e("VideoPlayerService", "attempted to unregister a nonregistered service");
}
}
@Nullable @Nullable
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
@ -156,41 +169,62 @@ public class VideoPlayerService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
Context context = this; Context context = this;
Log.v(TAG, "onStartCommand..."); 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(); playVideo();
return START_STICKY; return START_STICKY;
} }
}
public void setCurrentVideo(Video video) public void setCurrentVideo(Video video) {
{
Log.v(TAG, "setCurrentVideo..."); Log.v(TAG, "setCurrentVideo...");
currentVideo = video; currentVideo = video;
} }
public void setCurrentStreamUrl(String streamUrl) public void setCurrentStreamUrl(String streamUrl) {
{
Log.v(TAG, "setCurrentStreamUrl..." + streamUrl); Log.v(TAG, "setCurrentStreamUrl..." + streamUrl);
currentStreamUrl = streamUrl; currentStreamUrl = streamUrl;
} }
//Playback speed control //Playback speed control
public void setPlayBackSpeed(float speed) { public void setPlayBackSpeed(float speed) {
Log.v(TAG, "setPlayBackSpeed..."); Log.v(TAG, "setPlayBackSpeed...");
player.setPlaybackParameters(new PlaybackParameters(speed)); 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() { public void playVideo() {
Context context = this; Context context = this;
// We need a valid URL
Log.v(TAG, "playVideo..."); Log.v(TAG, "playVideo...");
// Produces DataSource instances through which media data is loaded. // Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(), // DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
Util.getUserAgent(getApplicationContext(), "PeerTube"), null); // 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. // This is the MediaSource representing the media to be played.
ExtractorMediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory) ExtractorMediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
@ -235,7 +269,8 @@ public class VideoPlayerService extends Service {
@Nullable @Nullable
@Override @Override
public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) { public Bitmap getCurrentLargeIcon(Player player,
PlayerNotificationManager.BitmapCallback callback) {
return null; return null;
} }
} }
@ -299,6 +334,7 @@ public class VideoPlayerService extends Service {
// pause playback on audio output change // pause playback on audio output change
private class BecomingNoisyReceiver extends BroadcastReceiver { private class BecomingNoisyReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) { 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_server_selection" android:id="@+id/appbar_server_selection"
@ -29,11 +34,19 @@
android:text="@string/no_data_available" android:text="@string/no_data_available"
android:visibility="gone" /> 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 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/serversSwipeRefreshLayout" android:id="@+id/serversSwipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@+id/appbar_server_selection"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@ -45,5 +58,5 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -31,7 +31,25 @@
android:layout_margin="@dimen/fab_margin" android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_baseline_add_24" /> 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> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,6 +1,21 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" 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 <FrameLayout
android:id="@+id/settings" 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:hint="@string/server_book_add_label"
android:inputType="textPersonName" /> android:inputType="textPersonName" />
<RelativeLayout <LinearLayout
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content"> android:layout_width="match_parent"
android:orientation="horizontal"
>
<EditText <EditText
android:layout_alignParentStart="true"
android:id="@+id/serverUrl" android:id="@+id/serverUrl"
android:layout_width="fill_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10" android:ems="10"
android:hint="@string/server_book_add_server_url" android:hint="@string/server_book_add_server_url"
android:inputType="textUri" android:inputType="textUri"
android:layout_toStartOf="@+id/pickServerUrl"/> />
<Button <Button
android:layout_alignParentEnd="true"
android:id="@+id/pickServerUrl" android:id="@+id/pickServerUrl"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/server_book_add_pick_server_button" /> android:text="@string/server_book_add_pick_server_button" />
</RelativeLayout> </LinearLayout>
<EditText <EditText
android:id="@+id/serverUsername" android:id="@+id/serverUsername"

View File

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

View File

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

View File

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

View File

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

View File

@ -7,15 +7,18 @@
card_view:cardElevation="0dp" card_view:cardElevation="0dp"
card_view:cardUseCompatPadding="true"> card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="12dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical">
android:padding="12dp">
<TextView <TextView
android:id="@+id/name" android:id="@+id/sl_row_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
@ -24,7 +27,7 @@
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline" /> android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline" />
<TextView <TextView
android:id="@+id/host" android:id="@+id/sl_row_host"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
@ -33,7 +36,7 @@
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" /> android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
<TextView <TextView
android:id="@+id/signupAllowed" android:id="@+id/sl_row_signup_allowed"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
@ -43,14 +46,45 @@
<TextView <TextView
android:id="@+id/shortDescription" android:id="@+id/sl_row_short_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:paddingTop="0dp" android:paddingTop="0dp"
android:visibility="gone"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" /> 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>
</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> </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" /> android:paddingEnd="12dp" />
<TextView <TextView
android:id="@+id/name" android:id="@+id/sl_row_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/thumb" android:layout_below="@id/thumb"
@ -74,7 +74,7 @@
android:id="@+id/videoMeta" android:id="@+id/videoMeta"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/name" android:layout_below="@+id/sl_row_name"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:layout_marginEnd="6dp" android:layout_marginEnd="6dp"
@ -101,7 +101,7 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginStart="-16dp" android:layout_marginStart="-16dp"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
android:layout_toEndOf="@+id/name" android:layout_toEndOf="@+id/sl_row_name"
android:background="@null" android:background="@null"
android:contentDescription="@string/descr_overflow_button" android:contentDescription="@string/descr_overflow_button"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"

View File

@ -24,7 +24,7 @@
<!-- Strings related to Settings --> <!-- Strings related to Settings -->
<string name="pref_title_peertube_server">خادوم PeerTube</string> <string name="pref_title_peertube_server">خادوم PeerTube</string>
<!-- Strings related to Video meta data --> <!-- 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_video_thumbnail">الصورة المصغرة للفيديو</string>
<string name="video_row_account_avatar">الصورة الرمزية للحساب</string> <string name="video_row_account_avatar">الصورة الرمزية للحساب</string>
<string name="pref_title_show_nsfw">عرض NSFW</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_license">الرخصة</string>
<string name="video_meta_button_language">اللغة</string> <string name="video_meta_button_language">اللغة</string>
<string name="video_meta_button_tags">العلامات</string> <string name="video_meta_button_tags">العلامات</string>
<string name="menu_video_options_playback_speed">سرعة التشغيل</string> <string name="menu_video_options_playback_speed" formatted="true">سرعة التشغيل (%1$s)</string>
<string name="menu_video_options_quality">الجودة</string> <string name="menu_video_options_quality" formatted="true">(%1$s) الجودة</string>
<string name="account_bottom_menu_videos">الفيديو</string> <string name="account_bottom_menu_videos">الفيديو</string>
<string name="account_bottom_menu_channels">القنوات</string> <string name="account_bottom_menu_channels">القنوات</string>
<string name="account_bottom_menu_about">حول</string> <string name="account_bottom_menu_about">حول</string>
@ -305,4 +305,51 @@
<string name="login_current_server_hint">الخادوم الحالي</string> <string name="login_current_server_hint">الخادوم الحالي</string>
<string name="video_speed_075">0.75x</string> <string name="video_speed_075">0.75x</string>
<string name="video_speed_125">1.25x</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> </resources>

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