Compare commits
135 Commits
Author | SHA1 | Date |
---|---|---|
semantic-release-bot | 3b88b039e9 | |
Stefan Schüller | 3915cc1bbb | |
Stefan Schüller | d98bf8c9a4 | |
Stefan Schüller | cb67a09604 | |
Stefan Schüller | cef0043693 | |
semantic-release-bot | 9839ed928b | |
Stefan Schüller | fe187e546d | |
semantic-release-bot | 7881b56b77 | |
Stefan Schüller | 064ec44d53 | |
Stefan Schüller | 2401de99a1 | |
Stefan Schüller | a31cd5494f | |
Stefan Schüller | 1d59aed5e7 | |
Stefan Schüller | 21261b0d09 | |
Petter Reinholdtsen | 7aee68213d | |
Nikita Epifanov | 5f16f194ab | |
Petter Reinholdtsen | 1468d9c490 | |
Jonathan Soares | 527c38584b | |
Jonathan Soares | 78adff6779 | |
Maxime Leroy | a2f5882211 | |
Digiwizkid | dba93fa9e8 | |
SC | aee7c1e32e | |
Stefan | f1640cfaa9 | |
Dave Heineman | 0a48cb5016 | |
Besnik Bleta | faa840a30f | |
Besnik Bleta | ca6c4da6e6 | |
Besnik Bleta | e3121769b5 | |
J. Lavoie | 5e28585fea | |
J. Lavoie | 8a2782a32b | |
Jeff Huang | e1be0e8a9f | |
J. Lavoie | 797fe99831 | |
Rex_sa | 89f978e3d4 | |
Allan Nordhøy | 0c51070f37 | |
Allan Nordhøy | 90b99c2437 | |
SC | 33e6fbb827 | |
Ihor Hordiichuk | 0153ff1a90 | |
GunChleoc | 80e67eebae | |
Sebastian Wilhelm Zarejko | db39e9442d | |
Software In Interlingua | 699da52489 | |
gnu-ewm | 270da310f7 | |
semantic-release-bot | 6a6405ccdc | |
Stefan Schüller | eb3b1eb7ad | |
Stefan Schüller | 04db4ceb7f | |
digiwizkid | c23c17d2ef | |
digiwizkid | 96ec510f40 | |
digiwizkid | c7893ddd38 | |
ssantos | 530b97979e | |
ssantos | abdcb87c3e | |
Stefan Schüller | 4b1efade40 | |
semantic-release-bot | 55a9acccd8 | |
Stefan Schüller | d37c788653 | |
Stefan Schüller | 1d25be1bde | |
semantic-release-bot | c3aef83ff3 | |
Stefan Schüller | 85f600b822 | |
Stefan Schüller | 0370a1261d | |
semantic-release-bot | 629445e2ae | |
Stefan Schüller | 8c9a7ff5c4 | |
Stefan Schüller | 0dd225423b | |
semantic-release-bot | 84a67f1e0b | |
Stefan Schüller | 15f9c59c03 | |
Stefan Schüller | 784c69fb3b | |
Stefan Schüller | 18e4949fe4 | |
semantic-release-bot | 8e4ad9c351 | |
Stefan Schüller | bc7e33da54 | |
Stefan Schüller | bbd318169a | |
Stefan Schüller | c8b23e9c1a | |
Stefan Schüller | d703438f40 | |
semantic-release-bot | 7c880655ae | |
Stefan Schüller | 19f7157b74 | |
Stefan Schüller | 5a19390e95 | |
Stefan Schüller | dd13dfaf3c | |
Stefan Schüller | e35d25a292 | |
Stefan Schüller | 47fe302f94 | |
Stefan Schüller | aefd8df5c6 | |
semantic-release-bot | 8d2946f956 | |
Stefan Schüller | b29afe52a2 | |
Pep Comeres | a8b670c248 | |
J. Lavoie | 98733491e6 | |
J. Lavoie | 7150d7d752 | |
J. Lavoie | d8c48cee40 | |
J. Lavoie | 8a146a82f6 | |
Stefan Schüller | 91b4869934 | |
Stefan Schüller | b83130125d | |
Pep Comeres | a082878cd3 | |
Danial Behzadi | 9c41d96930 | |
Ihor Hordiichuk | 9eda11ed4f | |
Ihor Hordiichuk | c0025bf2ae | |
Oğuz Ersen | c91da28639 | |
Danial Behzadi | 0cd5a39e80 | |
Jeff Huang | 059a020636 | |
Oğuz Ersen | 42de46cf58 | |
Rex_sa | 61d0a7747f | |
Stefan Schueller | 0d720105ce | |
Stefan Schüller | 6d4f6c9979 | |
semantic-release-bot | 34f0046bdd | |
Stefan Schüller | 0d4fae005d | |
Stefan Schueller | 28198895fb | |
Stefan Schueller | 37229a0f48 | |
Petter Reinholdtsen | 2742ed6064 | |
Petter Reinholdtsen | 08393e8275 | |
Andrei Stepanov | 49a579dc30 | |
Daimar Stein | 60eca32501 | |
Oymate | 45ac67333c | |
Danial Behzadi | 7648633684 | |
Danial Behzadi | 514cd65539 | |
Dronrs | 490d8e30a8 | |
Oğuz Ersen | 3f51020174 | |
Mickaël Sibelle | 85c8c8e8a8 | |
SC | cc6b42474e | |
Gabriel Cardoso | 694c52e6be | |
m51d | b98e2ae0be | |
m51d | ada03b3cb8 | |
Nikita Epifanov | 6f4ad5d639 | |
SC | daebed8bb5 | |
SC | 60abc3b9fe | |
SC | 4bd03934b3 | |
Jeff Huang | 8280bfb0a4 | |
Ihor Hordiichuk | 3ec9283093 | |
Ihor Hordiichuk | 68a57c8649 | |
J. Lavoie | ed28fe3789 | |
Oğuz Ersen | 42a01d45bc | |
J. Lavoie | 6707cec7e5 | |
J. Lavoie | 1995e623bd | |
J. Lavoie | 5fe4353db1 | |
J. Lavoie | 76ef906e36 | |
J. Lavoie | 18e1221aba | |
J. Lavoie | a5b7279bfc | |
Allan Nordhøy | 672760acce | |
Jeff Huang | fc07026a0a | |
Oğuz Ersen | 1202decd38 | |
J. Lavoie | 5c9c90cc05 | |
Rex_sa | 9453b5a2ef | |
Manuel González | cceec2f6ff | |
Ivano Peddis | 697044fba3 | |
Ivano Peddis | 792bf89bb0 | |
abidin toumi | 96246dc696 |
|
@ -108,6 +108,13 @@ buildDebug:
|
|||
script:
|
||||
- bundle exec fastlane buildDebug
|
||||
|
||||
testFastlane:
|
||||
stage: test
|
||||
script:
|
||||
- ./ci-scripts/validate-play-store-lang.sh
|
||||
tags:
|
||||
- docker
|
||||
|
||||
testDebug:
|
||||
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
stage: test
|
||||
|
|
174
CHANGELOG.md
174
CHANGELOG.md
|
@ -1,3 +1,177 @@
|
|||
## 1.12.2 (2022-04-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Removed videos, fastlane cant upload them d98bf8c
|
||||
* Update fastlane cef0043
|
||||
|
||||
## 1.12.1 (2022-04-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Too long play store update notes fe187e5
|
||||
|
||||
# 1.12.0 (2022-04-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lang:** Weblate translation (Albanian) faa840a
|
||||
* **lang:** Weblate translation (Albanian) e312176
|
||||
* **lang:** Weblate translation (French) a2f5882
|
||||
* **lang:** Weblate translation (Portuguese (Brazil)) 527c385
|
||||
* **lang:** Weblate translation (Portuguese) 33e6fbb
|
||||
* **lang:** Weblate translation (Portuguese) 530b979
|
||||
* **lang:** Weblate translation (Albanian) ca6c4da
|
||||
* **lang:** Weblate translation (Arabic) 89f978e
|
||||
* **lang:** Weblate translation (Bengali) dba93fa
|
||||
* **lang:** Weblate translation (Chinese (Traditional)) e1be0e8
|
||||
* **lang:** Weblate translation (English) 90b99c2
|
||||
* **lang:** Weblate translation (French) 797fe99
|
||||
* **lang:** Weblate translation (Gaelic) 80e67ee
|
||||
* **lang:** Weblate translation (German) 8a2782a
|
||||
* **lang:** Weblate translation (Italian) 5e28585
|
||||
* **lang:** Weblate translation (Norwegian Bokmål) 7aee682
|
||||
* **lang:** Weblate translation (Norwegian Bokmål) 1468d9c
|
||||
* **lang:** Weblate translation (Norwegian Bokmål) 0c51070
|
||||
* **lang:** Weblate translation (Polish) db39e94
|
||||
* **lang:** Weblate translation (Polish) 270da31
|
||||
* **lang:** Weblate translation (Portuguese (Brazil)) 78adff6
|
||||
* **lang:** Weblate translation (Portuguese) aee7c1e
|
||||
* **lang:** Weblate translation (Portuguese) abdcb87
|
||||
* **lang:** Weblate translation (Russian) 5f16f19
|
||||
* **lang:** Weblate translation (Ukrainian) 0153ff1
|
||||
* Remove unsupported play store language pt a31cd54
|
||||
* set required (im)mutable flag when creating pending intent 0a48cb5
|
||||
* Shortened too long title fr-FR 2401de9
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lang:** Added translation using Weblate (Interlingua) 699da52
|
||||
* weblate merge 21261b0
|
||||
|
||||
# 1.11.0 (2022-01-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Open playlist video in player, string update c23c17d
|
||||
|
||||
## 1.10.4 (2022-01-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Temp removal of videos in fastlane config 1d25be1
|
||||
|
||||
## 1.10.3 (2022-01-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed bad video URL 0370a12
|
||||
|
||||
## 1.10.2 (2022-01-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed bad video URL 0dd2254
|
||||
|
||||
## 1.10.1 (2022-01-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed broken languages 784c69f
|
||||
* Removed unsupported language in Google Play store and added test script to ci 18e4949
|
||||
|
||||
# 1.10.0 (2022-01-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lang:** Weblate translation (Persian) 9c41d96
|
||||
* **lang:** Weblate translation (Turkish) c91da28
|
||||
* **lang:** Weblate translation (Ukrainian) c0025bf
|
||||
* **lang:** Weblate translation (Arabic) 61d0a77
|
||||
* **lang:** Weblate translation (Catalan) a8b670c
|
||||
* **lang:** Weblate translation (Chinese (Traditional)) 059a020
|
||||
* **lang:** Weblate translation (Finnish) 9873349
|
||||
* **lang:** Weblate translation (French) 8a146a8
|
||||
* **lang:** Weblate translation (German) d8c48ce
|
||||
* **lang:** Weblate translation (Italian) 7150d7d
|
||||
* **lang:** Weblate translation (Persian) 0cd5a39
|
||||
* **lang:** Weblate translation (Turkish) 42de46c
|
||||
* **lang:** Weblate translation (Ukrainian) 9eda11e
|
||||
* Removed unsupported language in Google Play store c8b23e9
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lang:** Added translation using Weblate (Catalan) a082878
|
||||
|
||||
# 1.9.0 (2022-01-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* New player, updates to newest android SDK, New details view, Removed torrent playback (stopped working correctly), Subscribe / unsubscribe if logged in 5a19390
|
||||
|
||||
## 1.8.4 (2022-01-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **lang:** Weblate translation (Chinese (Traditional)) 8280bfb
|
||||
* **lang:** Weblate translation (French) 85c8c8e
|
||||
* **lang:** Weblate translation (French) 6707cec
|
||||
* **lang:** Weblate translation (Indonesian) ada03b3
|
||||
* **lang:** Weblate translation (Italian) ed28fe3
|
||||
* **lang:** Weblate translation (Norwegian Bokmål) 2742ed6
|
||||
* **lang:** Weblate translation (Persian) 7648633
|
||||
* **lang:** Weblate translation (Portuguese (Brazil)) 60eca32
|
||||
* **lang:** Weblate translation (Portuguese (Portugal)) cc6b424
|
||||
* **lang:** Weblate translation (Portuguese) daebed8
|
||||
* **lang:** Weblate translation (Portuguese) 4bd0393
|
||||
* **lang:** Weblate translation (Russian) 49a579d
|
||||
* **lang:** Weblate translation (Sardinian) 792bf89
|
||||
* **lang:** Weblate translation (Spanish) cceec2f
|
||||
* **lang:** Weblate translation (Turkish) 42a01d4
|
||||
* **lang:** Weblate translation (Ukrainian) 68a57c8
|
||||
* **lang:** Weblate translation (Arabic) 9453b5a
|
||||
* **lang:** Weblate translation (Arabic) 96246dc
|
||||
* **lang:** Weblate translation (Bengali) 45ac673
|
||||
* **lang:** Weblate translation (Chinese (Simplified)) 490d8e3
|
||||
* **lang:** Weblate translation (Chinese (Traditional)) fc07026
|
||||
* **lang:** Weblate translation (Finnish) 5fe4353
|
||||
* **lang:** Weblate translation (French) 5c9c90c
|
||||
* **lang:** Weblate translation (German) a5b7279
|
||||
* **lang:** Weblate translation (Indonesian) b98e2ae
|
||||
* **lang:** Weblate translation (Italian) 76ef906
|
||||
* **lang:** Weblate translation (Norwegian Bokmål) 08393e8
|
||||
* **lang:** Weblate translation (Norwegian Bokmål) 672760a
|
||||
* **lang:** Weblate translation (Persian) 514cd65
|
||||
* **lang:** Weblate translation (Portuguese (Brazil)) 694c52e
|
||||
* **lang:** Weblate translation (Portuguese) 60abc3b
|
||||
* **lang:** Weblate translation (Portuguese) 1995e62
|
||||
* **lang:** Weblate translation (Russian) 6f4ad5d
|
||||
* **lang:** Weblate translation (Sardinian) 697044f
|
||||
* **lang:** Weblate translation (Spanish) 18e1221
|
||||
* **lang:** Weblate translation (Turkish) 3f51020
|
||||
* **lang:** Weblate translation (Turkish) 1202dec
|
||||
* **lang:** Weblate translation (Ukrainian) 3ec9283
|
||||
|
||||
## 1.8.3 (2021-12-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* new play store shorter app name requirement 37229a0
|
||||
|
||||
## 1.8.2 (2021-12-27)
|
||||
|
||||
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
2. Locally switch to develop
|
||||
3. Pull github develop
|
||||
4. Pull weblate develop
|
||||
5. Push to develop gitlab and github
|
||||
5. Push to develop gitlab
|
||||
6. Merge develop into master and merge
|
||||
7. Wait for Release Build and release to play store
|
||||
8. Wait for gitlab -> github sync
|
||||
9. Run publishGithub
|
||||
10. Merge master into develop, push to github
|
||||
|
||||
|
||||
## fastlane update
|
||||
## fastlane update (install ruby2.7 and "gem-2.7 install bundler")
|
||||
```
|
||||
bundle update
|
||||
bundle-2.7 update
|
||||
```
|
|
@ -1,6 +1,5 @@
|
|||
FROM gradle:7-jdk16
|
||||
|
||||
|
||||
# get link at bottom of https://developer.android.com/studio
|
||||
ENV ANDROID_SDK_URL https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip
|
||||
ENV ANDROID_SDK_CHECKSUM 124f2d5115eee365df6cf3228ffbca6fc3911d16f8025bebd5b1c6e2fcfa7faf
|
||||
|
@ -8,7 +7,7 @@ ENV ANDROID_SDK_CHECKSUM 124f2d5115eee365df6cf3228ffbca6fc3911d16f8025bebd5b1c6e
|
|||
# higher version casues Warning: Failed to find package
|
||||
ENV ANDROID_BUILD_TOOLS_VERSION 30.0.2
|
||||
ENV ANDROID_SDK_ROOT /usr/local/android-sdk-linux
|
||||
ENV ANDROID_VERSION 30
|
||||
ENV ANDROID_VERSION 32
|
||||
# ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
||||
ENV PATH ${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin
|
||||
|
||||
|
|
60
Gemfile.lock
60
Gemfile.lock
|
@ -8,23 +8,23 @@ GEM
|
|||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.543.0)
|
||||
aws-sdk-core (3.125.0)
|
||||
aws-partitions (1.573.0)
|
||||
aws-sdk-core (3.130.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.53.0)
|
||||
aws-sdk-core (~> 3, >= 3.125.0)
|
||||
aws-sdk-kms (1.55.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.110.0)
|
||||
aws-sdk-core (~> 3, >= 3.125.0)
|
||||
aws-sdk-s3 (1.113.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.0.3)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
|
@ -36,17 +36,18 @@ GEM
|
|||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.89.0)
|
||||
faraday (1.8.0)
|
||||
excon (0.92.2)
|
||||
faraday (1.10.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.1)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
|
@ -55,14 +56,17 @@ GEM
|
|||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.199.0)
|
||||
fastlane (2.205.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
|
@ -102,9 +106,9 @@ GEM
|
|||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.14.0)
|
||||
google-apis-androidpublisher_v3 (0.17.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.4.1)
|
||||
google-apis-core (0.4.2)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
|
@ -113,19 +117,19 @@ GEM
|
|||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.9.0)
|
||||
google-apis-iamcredentials_v1 (0.10.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.6.0)
|
||||
google-apis-playcustomapp_v1 (0.7.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.10.0)
|
||||
google-apis-storage_v1 (0.11.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.35.0)
|
||||
google-cloud-storage (1.36.1)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
|
@ -133,8 +137,8 @@ GEM
|
|||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.1.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
googleauth (1.1.2)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
|
@ -144,7 +148,7 @@ GEM
|
|||
http-cookie (1.0.4)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.4.0)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.1)
|
||||
jwt (2.3.0)
|
||||
memoist (0.16.2)
|
||||
|
@ -169,9 +173,9 @@ GEM
|
|||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.16.0)
|
||||
signet (0.16.1)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
faraday (>= 0.17.5, < 3.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
|
@ -188,7 +192,7 @@ GEM
|
|||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unf_ext (0.0.8.1)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
|
@ -211,4 +215,4 @@ DEPENDENCIES
|
|||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.3
|
||||
2.3.10
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.8.2
|
||||
1.12.2
|
||||
|
|
|
@ -39,15 +39,15 @@ else {
|
|||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 32
|
||||
buildToolsVersion "30.0.2"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "net.schueller.peertube"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 1068
|
||||
versionName "1.8.2"
|
||||
targetSdkVersion 32
|
||||
versionCode 1080
|
||||
versionName "1.12.2"
|
||||
buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L'
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
@ -94,10 +94,10 @@ android {
|
|||
|
||||
}
|
||||
|
||||
def room_version = "2.3.0"
|
||||
def lifecycleVersion = '2.3.1'
|
||||
def exoplayer = '2.12.3'
|
||||
def fragment_version = "1.3.6"
|
||||
def room_version = "2.4.0"
|
||||
def lifecycleVersion = '2.4.0'
|
||||
def exoplayer = '2.16.1'
|
||||
def fragment_version = "1.4.0"
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
@ -105,8 +105,8 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
// Layouts and design
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
@ -118,7 +118,7 @@ dependencies {
|
|||
implementation 'com.mikepenz:fontawesome-typeface:5.9.0.2-kotlin@aar'
|
||||
|
||||
// http client / REST
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
|
||||
// image downloading and caching library
|
||||
|
|
|
@ -1,78 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="net.schueller.peertube">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="net.schueller.peertube">
|
||||
<!-- required to play video in background via notification -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- connect to peertube server -->
|
||||
<uses-permission android:name="android.permission.INTERNET" /> <!-- required for torrent downloading -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <!-- connect to peertube server -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/> <!-- required for torrent downloading -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:name=".application.AppApplication"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
android:name=".application.AppApplication"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<!-- Server Address Book -->
|
||||
<activity
|
||||
android:name=".activity.ServerAddressBookActivity"
|
||||
android:label="@string/title_activity_server_address_book"
|
||||
android:theme="@style/AppTheme.NoActionBar" /> <!-- Video Lists -->
|
||||
android:name=".activity.ServerAddressBookActivity"
|
||||
android:label="@string/title_activity_server_address_book"
|
||||
android:theme="@style/AppTheme.NoActionBar"/> <!-- Video Lists -->
|
||||
<activity
|
||||
android:name=".activity.VideoListActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:exported="true">
|
||||
android:name=".activity.VideoListActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<action android:name="android.intent.action.SEARCH"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable"/>
|
||||
</activity> <!-- Video Player -->
|
||||
<activity
|
||||
android:name=".activity.VideoPlayActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
|
||||
android:label="@string/title_activity_video_play"
|
||||
android:launchMode="singleInstance"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:theme="@style/AppTheme.NoActionBar" /> <!-- Settings -->
|
||||
android:name=".activity.VideoPlayActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
|
||||
android:label="@string/title_activity_video_play"
|
||||
android:launchMode="singleInstance"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<!-- Playlist -->
|
||||
<activity android:name=".activity.PlaylistActivity"
|
||||
android:label="Playlist"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<!-- Settings -->
|
||||
<activity
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
android:theme="@style/AppTheme.NoActionBar" /> <!-- Server Selection -->
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
android:theme="@style/AppTheme.NoActionBar"/> <!-- Server Selection -->
|
||||
<activity
|
||||
android:name=".activity.SearchServerActivity"
|
||||
android:label="@string/title_activity_select_server"
|
||||
android:theme="@style/AppTheme.NoActionBar" /> <!-- Me -->
|
||||
android:name=".activity.SearchServerActivity"
|
||||
android:label="@string/title_activity_select_server"
|
||||
android:theme="@style/AppTheme.NoActionBar"/> <!-- Me -->
|
||||
<activity
|
||||
android:name=".activity.MeActivity"
|
||||
android:label="@string/title_activity_me"
|
||||
android:theme="@style/AppTheme.NoActionBar" /> <!-- Account -->
|
||||
android:name=".activity.MeActivity"
|
||||
android:label="@string/title_activity_me"
|
||||
android:theme="@style/AppTheme.NoActionBar"/> <!-- Account -->
|
||||
<activity
|
||||
android:name=".activity.AccountActivity"
|
||||
android:label="@string/title_activity_account"
|
||||
android:theme="@style/AppTheme.NoActionBar" /> <!-- Content provider for search suggestions -->
|
||||
android:name=".activity.AccountActivity"
|
||||
android:label="@string/title_activity_account"
|
||||
android:theme="@style/AppTheme.NoActionBar"/> <!-- Content provider for search suggestions -->
|
||||
<provider
|
||||
android:name=".provider.SearchSuggestionsProvider"
|
||||
android:authorities="net.schueller.peertube.provider.SearchSuggestionsProvider"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
android:name=".provider.SearchSuggestionsProvider"
|
||||
android:authorities="net.schueller.peertube.provider.SearchSuggestionsProvider"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
|
||||
<service android:name=".service.VideoPlayerService" />
|
||||
<service android:name=".service.VideoPlayerService"/>
|
||||
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver"
|
||||
android:exported="true">
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
|
|
@ -175,11 +175,11 @@ public class AccountActivity extends CommonActivity {
|
|||
if (response.isSuccessful()) {
|
||||
Account account = response.body();
|
||||
|
||||
String owner = MetaDataHelper.getOwnerString(account.getName(),
|
||||
account.getHost(),
|
||||
AccountActivity.this
|
||||
String owner = MetaDataHelper.getOwnerString(account,
|
||||
AccountActivity.this, true
|
||||
);
|
||||
|
||||
|
||||
// set view data
|
||||
TextView ownerStringView = findViewById(R.id.account_owner_string);
|
||||
ownerStringView.setText(owner);
|
||||
|
|
|
@ -27,7 +27,9 @@ import android.view.View;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.helper.ErrorHelper;
|
||||
|
@ -36,18 +38,12 @@ import net.schueller.peertube.model.Me;
|
|||
import net.schueller.peertube.network.GetUserService;
|
||||
import net.schueller.peertube.network.RetrofitInstance;
|
||||
import net.schueller.peertube.network.Session;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static net.schueller.peertube.application.AppApplication.getContext;
|
||||
|
||||
public class MeActivity extends CommonActivity {
|
||||
|
@ -85,11 +81,16 @@ public class MeActivity extends CommonActivity {
|
|||
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_baseline_close_24);
|
||||
|
||||
LinearLayout account = findViewById(R.id.a_me_account_line);
|
||||
LinearLayout playlist = findViewById(R.id.a_me_playlist);
|
||||
LinearLayout settings = findViewById(R.id.a_me_settings);
|
||||
LinearLayout help = findViewById(R.id.a_me_helpnfeedback);
|
||||
|
||||
TextView logout = findViewById(R.id.a_me_logout);
|
||||
|
||||
playlist.setOnClickListener(view -> {
|
||||
Intent playlistActivity = new Intent(getContext(), PlaylistActivity.class);
|
||||
startActivity(playlistActivity);
|
||||
});
|
||||
|
||||
settings.setOnClickListener(view -> {
|
||||
Intent settingsActivity = new Intent(getContext(), SettingsActivity.class);
|
||||
|
@ -124,7 +125,7 @@ public class MeActivity extends CommonActivity {
|
|||
|
||||
call.enqueue(new Callback<Me>() {
|
||||
|
||||
LinearLayout account = findViewById(R.id.a_me_account_line);
|
||||
final LinearLayout account = findViewById(R.id.a_me_account_line);
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Me> call, @NonNull Response<Me> response) {
|
||||
|
@ -162,7 +163,7 @@ public class MeActivity extends CommonActivity {
|
|||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Me> call, @NonNull Throwable t) {
|
||||
ErrorHelper.showToastFromCommunicationError( MeActivity.this, t );
|
||||
ErrorHelper.showToastFromCommunicationError(MeActivity.this, t);
|
||||
account.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.AlertDialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.adapter.MultiViewRecyclerViewHolder
|
||||
import net.schueller.peertube.adapter.PlaylistAdapter
|
||||
import net.schueller.peertube.database.Video
|
||||
import net.schueller.peertube.database.VideoViewModel
|
||||
import net.schueller.peertube.databinding.ActivityPlaylistBinding
|
||||
|
||||
class PlaylistActivity : CommonActivity() {
|
||||
|
||||
private val TAG = "PlaylistAct"
|
||||
|
||||
private val mVideoViewModel: VideoViewModel by viewModels()
|
||||
|
||||
private lateinit var mBinding: ActivityPlaylistBinding
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
finish() // close this activity as oppose to navigating up
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mBinding = ActivityPlaylistBinding.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()
|
||||
}
|
||||
|
||||
private fun onVideoClick(video: Video) {
|
||||
val intent = Intent(this, VideoPlayActivity::class.java)
|
||||
intent.putExtra(MultiViewRecyclerViewHolder.EXTRA_VIDEOID, video.videoUUID)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun showServers() {
|
||||
val adapter = PlaylistAdapter(mutableListOf(), { onVideoClick(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@PlaylistActivity)
|
||||
.setTitle(getString(R.string.remove_video))
|
||||
.setMessage(getString(R.string.remove_video_warning_message))
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val video = adapter.getVideoAtPosition(position)
|
||||
// Delete the video
|
||||
mVideoViewModel.delete(video)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) }
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
helper.attachToRecyclerView(mBinding.serverListRecyclerview)
|
||||
|
||||
// Update the cached copy of the words in the adapter.
|
||||
mVideoViewModel.allVideos.observe(this, { videos: List<Video> ->
|
||||
adapter.setVideos(videos)
|
||||
})
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
|
@ -41,7 +41,7 @@ import java.util.*
|
|||
|
||||
class ServerAddressBookActivity : CommonActivity() {
|
||||
|
||||
private val TAG = "ServerAddressBookActivity"
|
||||
private val TAG = "ServerAddBookAct"
|
||||
|
||||
private val mServerViewModel: ServerViewModel by viewModels()
|
||||
private var addServerFragment: AddServerFragment? = null
|
||||
|
@ -133,15 +133,15 @@ class ServerAddressBookActivity : CommonActivity() {
|
|||
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
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
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) }
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) }
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show()
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package net.schueller.peertube.activity
|
|||
import android.Manifest.permission
|
||||
import android.R.drawable
|
||||
import android.R.string
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog.Builder
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
|
@ -33,6 +34,7 @@ import android.view.MenuItem
|
|||
import android.view.MenuItem.OnActionExpandListener
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.SearchView.OnSuggestionListener
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
|
@ -116,7 +118,7 @@ class VideoListActivity : CommonActivity() {
|
|||
Builder(this@VideoListActivity)
|
||||
.setTitle(getString(R.string.clear_search_history))
|
||||
.setMessage(getString(R.string.clear_search_history_prompt))
|
||||
.setPositiveButton(string.yes) { _, _ ->
|
||||
.setPositiveButton(string.ok) { _, _ ->
|
||||
val suggestions = SearchRecentSuggestions(
|
||||
applicationContext,
|
||||
SearchSuggestionsProvider.AUTHORITY,
|
||||
|
@ -124,7 +126,7 @@ class VideoListActivity : CommonActivity() {
|
|||
)
|
||||
suggestions.clearHistory()
|
||||
}
|
||||
.setNegativeButton(string.no, null)
|
||||
.setNegativeButton(string.cancel, null)
|
||||
.setIcon(drawable.ic_dialog_alert)
|
||||
.show()
|
||||
true
|
||||
|
@ -160,8 +162,7 @@ class VideoListActivity : CommonActivity() {
|
|||
position
|
||||
) as Cursor
|
||||
return cursor.getString(
|
||||
cursor
|
||||
.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)
|
||||
cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -178,15 +179,26 @@ class VideoListActivity : CommonActivity() {
|
|||
stopService(Intent(this, VideoPlayerService::class.java))
|
||||
}
|
||||
|
||||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == SWITCH_INSTANCE) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
loadVideos(currentStart, count, sort, filter)
|
||||
}
|
||||
// public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
// super.onActivityResult(requestCode, resultCode, data)
|
||||
// if (requestCode == SWITCH_INSTANCE) {
|
||||
// if (resultCode == RESULT_OK) {
|
||||
// loadVideos(currentStart, count, sort, filter)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private var resultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
loadVideos(currentStart, count, sort, filter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openActivityForResult(intent: Intent) {
|
||||
resultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
|
@ -213,7 +225,7 @@ class VideoListActivity : CommonActivity() {
|
|||
}
|
||||
id.action_server_address_book -> {
|
||||
val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java)
|
||||
this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE)
|
||||
openActivityForResult(addressBookActivityIntent)
|
||||
return false
|
||||
}
|
||||
else -> {
|
||||
|
@ -461,7 +473,7 @@ class VideoListActivity : CommonActivity() {
|
|||
// new IconicsDrawable(this, FontAwesome.Icon.faw_user_circle));
|
||||
|
||||
// Click Listener
|
||||
navigation.setOnNavigationItemSelectedListener { menuItem: MenuItem ->
|
||||
navigation.setOnItemSelectedListener { menuItem: MenuItem ->
|
||||
when (menuItem.itemId) {
|
||||
id.navigation_overview -> {
|
||||
// TODO
|
||||
|
@ -470,7 +482,7 @@ class VideoListActivity : CommonActivity() {
|
|||
loadOverview(currentPage)
|
||||
overViewActive = true
|
||||
}
|
||||
return@setOnNavigationItemSelectedListener true
|
||||
return@setOnItemSelectedListener true
|
||||
}
|
||||
id.navigation_trending -> {
|
||||
//Log.v(TAG, "navigation_trending");
|
||||
|
@ -482,7 +494,7 @@ class VideoListActivity : CommonActivity() {
|
|||
subscriptions = false
|
||||
loadVideos(currentStart, count, sort, filter)
|
||||
}
|
||||
return@setOnNavigationItemSelectedListener true
|
||||
return@setOnItemSelectedListener true
|
||||
}
|
||||
id.navigation_recent -> {
|
||||
if (!isLoading) {
|
||||
|
@ -493,7 +505,7 @@ class VideoListActivity : CommonActivity() {
|
|||
subscriptions = false
|
||||
loadVideos(currentStart, count, sort, filter)
|
||||
}
|
||||
return@setOnNavigationItemSelectedListener true
|
||||
return@setOnItemSelectedListener true
|
||||
}
|
||||
id.navigation_local -> {
|
||||
//Log.v(TAG, "navigation_trending");
|
||||
|
@ -505,15 +517,15 @@ class VideoListActivity : CommonActivity() {
|
|||
subscriptions = false
|
||||
loadVideos(currentStart, count, sort, filter)
|
||||
}
|
||||
return@setOnNavigationItemSelectedListener true
|
||||
return@setOnItemSelectedListener true
|
||||
}
|
||||
id.navigation_subscriptions -> //Log.v(TAG, "navigation_subscriptions");
|
||||
if (!Session.getInstance().isLoggedIn) {
|
||||
// Intent intent = new Intent(this, LoginActivity.class);
|
||||
// this.startActivity(intent);
|
||||
val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java)
|
||||
this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE)
|
||||
return@setOnNavigationItemSelectedListener false
|
||||
openActivityForResult(addressBookActivityIntent)
|
||||
return@setOnItemSelectedListener false
|
||||
} else {
|
||||
if (!isLoading) {
|
||||
overViewActive = false
|
||||
|
@ -523,7 +535,7 @@ class VideoListActivity : CommonActivity() {
|
|||
subscriptions = true
|
||||
loadVideos(currentStart, count, sort, filter)
|
||||
}
|
||||
return@setOnNavigationItemSelectedListener true
|
||||
return@setOnItemSelectedListener true
|
||||
}
|
||||
}
|
||||
false
|
||||
|
@ -574,6 +586,5 @@ class VideoListActivity : CommonActivity() {
|
|||
|
||||
const val EXTRA_VIDEOID = "VIDEOID"
|
||||
const val EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST"
|
||||
const val SWITCH_INSTANCE = 2
|
||||
}
|
||||
}
|
|
@ -1,502 +0,0 @@
|
|||
/*
|
||||
* 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.annotation.SuppressLint;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.fragment.VideoMetaDataFragment;
|
||||
import net.schueller.peertube.fragment.VideoPlayerFragment;
|
||||
import net.schueller.peertube.service.VideoPlayerService;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
|
||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PAUSE;
|
||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PLAY;
|
||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
|
||||
import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode;
|
||||
|
||||
public class VideoPlayActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "VideoPlayActivity";
|
||||
|
||||
static boolean floatMode = false;
|
||||
|
||||
private static final int REQUEST_CODE = 101;
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
//This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode.
|
||||
@SuppressLint("NewApi")
|
||||
public void makePipControls() {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
ArrayList<RemoteAction> actions = new ArrayList<>();
|
||||
|
||||
Intent actionIntent = new Intent(getString(R.string.app_background_audio));
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
||||
@SuppressLint({"NewApi", "LocalSuppress"}) Icon icon = Icon.createWithResource(getApplicationContext(), android.R.drawable.stat_sys_speakerphone);
|
||||
@SuppressLint({"NewApi", "LocalSuppress"}) RemoteAction remoteAction = new RemoteAction(icon, "close pip", "from pip window custom command", pendingIntent);
|
||||
actions.add(remoteAction);
|
||||
|
||||
actionIntent = new Intent(ACTION_STOP);
|
||||
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
||||
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_stop);
|
||||
remoteAction = new RemoteAction(icon, "play", "stop the media", pendingIntent);
|
||||
actions.add(remoteAction);
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
if (videoPlayerFragment.isPaused()) {
|
||||
Log.e(TAG, "setting actions with play button");
|
||||
actionIntent = new Intent(ACTION_PLAY);
|
||||
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
||||
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_play);
|
||||
remoteAction = new RemoteAction(icon, "play", "play the media", pendingIntent);
|
||||
} else {
|
||||
Log.e(TAG, "setting actions with pause button");
|
||||
actionIntent = new Intent(ACTION_PAUSE);
|
||||
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
||||
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause);
|
||||
remoteAction = new RemoteAction(icon, "pause", "pause the media", pendingIntent);
|
||||
}
|
||||
actions.add(remoteAction);
|
||||
|
||||
|
||||
//add custom actions to pip window
|
||||
PictureInPictureParams params =
|
||||
new PictureInPictureParams.Builder()
|
||||
.setActions(actions)
|
||||
.build();
|
||||
setPictureInPictureParams(params);
|
||||
}
|
||||
|
||||
public void changedToPipMode() {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
videoPlayerFragment.showControls(false);
|
||||
//create custom actions
|
||||
makePipControls();
|
||||
|
||||
//setup receiver to handle customer actions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STOP);
|
||||
filter.addAction(ACTION_PAUSE);
|
||||
filter.addAction(ACTION_PLAY);
|
||||
filter.addAction((getString(R.string.app_background_audio)));
|
||||
receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
assert action != null;
|
||||
if (action.equals(ACTION_PAUSE)) {
|
||||
videoPlayerFragment.pauseVideo();
|
||||
makePipControls();
|
||||
}
|
||||
if (action.equals(ACTION_PLAY)) {
|
||||
videoPlayerFragment.unPauseVideo();
|
||||
makePipControls();
|
||||
}
|
||||
|
||||
if (action.equals(getString(R.string.app_background_audio))) {
|
||||
unregisterReceiver(receiver);
|
||||
finish();
|
||||
}
|
||||
if (action.equals(ACTION_STOP)) {
|
||||
unregisterReceiver(receiver);
|
||||
finishAndRemoveTask();
|
||||
}
|
||||
}
|
||||
};
|
||||
registerReceiver(receiver, filter);
|
||||
|
||||
Log.v(TAG, "switched to pip ");
|
||||
floatMode = true;
|
||||
videoPlayerFragment.showControls(false);
|
||||
}
|
||||
|
||||
public void changedToNormalMode() {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
videoPlayerFragment.showControls(true);
|
||||
if (receiver != null) {
|
||||
unregisterReceiver(receiver);
|
||||
}
|
||||
Log.v(TAG, "switched to normal");
|
||||
floatMode = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set theme
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
setTheme(getResources().getIdentifier(
|
||||
sharedPref.getString(
|
||||
getString(R.string.pref_theme_key),
|
||||
getString(R.string.app_default_theme)
|
||||
),
|
||||
"style",
|
||||
getPackageName())
|
||||
);
|
||||
|
||||
setContentView(R.layout.activity_video_play);
|
||||
|
||||
// get video ID
|
||||
Intent intent = getIntent();
|
||||
String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
String playingVideo = videoPlayerFragment.getVideoUuid();
|
||||
Log.v(TAG, "oncreate click: " + videoUuid + " is trying to replace: " + playingVideo);
|
||||
|
||||
if (TextUtils.isEmpty(playingVideo)) {
|
||||
Log.v(TAG, "oncreate no video currently playing");
|
||||
videoPlayerFragment.start(videoUuid);
|
||||
} else if (!playingVideo.equals(videoUuid)) {
|
||||
Log.v(TAG, "oncreate different video playing currently");
|
||||
videoPlayerFragment.stopVideo();
|
||||
videoPlayerFragment.start(videoUuid);
|
||||
} else {
|
||||
Log.v(TAG, "oncreate same video playing currently");
|
||||
}
|
||||
|
||||
// if we are in landscape set the video to fullscreen
|
||||
int orientation = this.getResources().getConfiguration().orientation;
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
setOrientation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
||||
assert videoPlayerFragment != null;
|
||||
String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
|
||||
Log.v(TAG, "new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.getVideoUuid());
|
||||
String playingVideo = videoPlayerFragment.getVideoUuid();
|
||||
|
||||
if (TextUtils.isEmpty(playingVideo)) {
|
||||
Log.v(TAG, "new intent no video currently playing");
|
||||
videoPlayerFragment.start(videoUuid);
|
||||
} else if (!playingVideo.equals(videoUuid)) {
|
||||
Log.v(TAG, "new intent different video playing currently");
|
||||
videoPlayerFragment.stopVideo();
|
||||
videoPlayerFragment.start(videoUuid);
|
||||
} else {
|
||||
Log.v(TAG, "new intent same video playing currently");
|
||||
}
|
||||
|
||||
// if we are in landscape set the video to fullscreen
|
||||
int orientation = this.getResources().getConfiguration().orientation;
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
setOrientation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
Log.v(TAG, "onConfigurationChanged()...");
|
||||
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
// Checking the orientation changes of the screen
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
setOrientation(true);
|
||||
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
setOrientation(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setOrientation(Boolean isLandscape) {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
||||
VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
|
||||
|
||||
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) {
|
||||
transaction.hide(videoMetaFragment);
|
||||
} else {
|
||||
transaction.show(videoMetaFragment);
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
videoPlayerFragment.setIsFullscreen(isLandscape);
|
||||
|
||||
if ( isLandscape ) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
videoPlayerFragment.destroyVideo();
|
||||
|
||||
super.onDestroy();
|
||||
Log.v(TAG, "onDestroy...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
Log.v(TAG, "onPause()...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
Log.v(TAG, "onResume()...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
videoPlayerFragment.stopVideo();
|
||||
|
||||
Log.v(TAG, "onStop()...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
Log.v(TAG, "onStart()...");
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onUserLeaveHint() {
|
||||
|
||||
Log.v(TAG, "onUserLeaveHint()...");
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
||||
VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
|
||||
|
||||
String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
assert backgroundBehavior != null;
|
||||
if ( videoMetaDataFragment.isLeaveAppExpected() )
|
||||
{
|
||||
super.onUserLeaveHint();
|
||||
return;
|
||||
}
|
||||
|
||||
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
|
||||
Log.v(TAG, "stop the video");
|
||||
|
||||
videoPlayerFragment.pauseVideo();
|
||||
stopService(new Intent(this, VideoPlayerService.class));
|
||||
super.onBackPressed();
|
||||
|
||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
|
||||
Log.v(TAG, "play the Audio");
|
||||
super.onBackPressed();
|
||||
|
||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
|
||||
Log.v(TAG, "play in floating video");
|
||||
//canEnterPIPMode makes sure API level is high enough
|
||||
if (canEnterPipMode(this)) {
|
||||
Log.v(TAG, "enabling pip");
|
||||
enterPipMode();
|
||||
} else {
|
||||
Log.v(TAG, "unable to use pip");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Deal with bad entries from older version
|
||||
Log.v(TAG, "No setting, fallback");
|
||||
super.onBackPressed();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// @RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@SuppressLint("NewApi")
|
||||
public void onBackPressed() {
|
||||
|
||||
Log.v(TAG, "onBackPressed()...");
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
assert videoPlayerFragment != null;
|
||||
|
||||
// copying Youtube behavior to have back button exit full screen.
|
||||
if (videoPlayerFragment.getIsFullscreen()) {
|
||||
Log.v(TAG, "exiting full screen");
|
||||
videoPlayerFragment.fullScreenToggle();
|
||||
return;
|
||||
}
|
||||
// pause video if pref is enabled
|
||||
if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) {
|
||||
videoPlayerFragment.pauseVideo();
|
||||
}
|
||||
|
||||
String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
|
||||
|
||||
assert backgroundBehavior != null;
|
||||
|
||||
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
|
||||
Log.v(TAG, "stop the video");
|
||||
videoPlayerFragment.pauseVideo();
|
||||
stopService(new Intent(this, VideoPlayerService.class));
|
||||
super.onBackPressed();
|
||||
|
||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
|
||||
Log.v(TAG, "play the Audio");
|
||||
super.onBackPressed();
|
||||
|
||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
|
||||
Log.v(TAG, "play in floating video");
|
||||
//canEnterPIPMode makes sure API level is high enough
|
||||
if (canEnterPipMode(this)) {
|
||||
Log.v(TAG, "enabling pip");
|
||||
enterPipMode();
|
||||
//fixes problem where back press doesn't bring up video list after returning from PIP mode
|
||||
Intent intentSettings = new Intent(this, VideoListActivity.class);
|
||||
this.startActivity(intentSettings);
|
||||
} else {
|
||||
Log.v(TAG, "Unable to enter PIP mode");
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
} else {
|
||||
// Deal with bad entries from older version
|
||||
Log.v(TAG, "No setting, fallback");
|
||||
super.onBackPressed();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public void enterPipMode() {
|
||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
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 =
|
||||
new PictureInPictureParams.Builder()
|
||||
.setAspectRatio( rational )
|
||||
// .setSourceRectHint(new Rect(0,500,400,600))
|
||||
.build();
|
||||
|
||||
enterPictureInPictureMode( mParams );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
||||
|
||||
if (videoPlayerFragment != null) {
|
||||
|
||||
if (isInPictureInPictureMode) {
|
||||
changedToPipMode();
|
||||
Log.v(TAG, "switched to pip ");
|
||||
videoPlayerFragment.useController(false);
|
||||
} else {
|
||||
changedToNormalMode();
|
||||
Log.v(TAG, "switched to normal");
|
||||
videoPlayerFragment.useController(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.e(TAG, "videoPlayerFragment is NULL");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
* 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 androidx.appcompat.app.AppCompatActivity
|
||||
import android.annotation.SuppressLint
|
||||
import net.schueller.peertube.fragment.VideoPlayerFragment
|
||||
import net.schueller.peertube.R
|
||||
import android.app.RemoteAction
|
||||
import android.app.PendingIntent
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.*
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import net.schueller.peertube.fragment.VideoMetaDataFragment
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.FrameLayout
|
||||
import android.util.TypedValue
|
||||
import android.view.WindowManager
|
||||
import net.schueller.peertube.service.VideoPlayerService
|
||||
import net.schueller.peertube.helper.VideoHelper
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import androidx.fragment.app.Fragment
|
||||
import net.schueller.peertube.fragment.VideoDescriptionFragment
|
||||
import java.util.ArrayList
|
||||
|
||||
class VideoPlayActivity : CommonActivity() {
|
||||
private var receiver: BroadcastReceiver? = null
|
||||
|
||||
//This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode.
|
||||
@SuppressLint("NewApi")
|
||||
fun makePipControls() {
|
||||
val fragmentManager = supportFragmentManager
|
||||
val videoPlayerFragment =
|
||||
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||
val actions = ArrayList<RemoteAction>()
|
||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
|
||||
var actionIntent = Intent(getString(R.string.app_background_audio))
|
||||
var pendingIntent =
|
||||
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, flags)
|
||||
@SuppressLint("NewApi", "LocalSuppress") var icon = Icon.createWithResource(
|
||||
applicationContext, android.R.drawable.stat_sys_speakerphone
|
||||
)
|
||||
@SuppressLint("NewApi", "LocalSuppress") var remoteAction =
|
||||
RemoteAction(icon!!, "close pip", "from pip window custom command", pendingIntent!!)
|
||||
actions.add(remoteAction)
|
||||
actionIntent = Intent(PlayerNotificationManager.ACTION_STOP)
|
||||
pendingIntent =
|
||||
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, flags)
|
||||
icon = Icon.createWithResource(
|
||||
applicationContext,
|
||||
com.google.android.exoplayer2.ui.R.drawable.exo_notification_stop
|
||||
)
|
||||
remoteAction = RemoteAction(icon, "play", "stop the media", pendingIntent)
|
||||
actions.add(remoteAction)
|
||||
assert(videoPlayerFragment != null)
|
||||
if (videoPlayerFragment!!.isPaused) {
|
||||
Log.e(TAG, "setting actions with play button")
|
||||
actionIntent = Intent(PlayerNotificationManager.ACTION_PLAY)
|
||||
pendingIntent =
|
||||
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, flags)
|
||||
icon = Icon.createWithResource(
|
||||
applicationContext,
|
||||
com.google.android.exoplayer2.ui.R.drawable.exo_notification_play
|
||||
)
|
||||
remoteAction = RemoteAction(icon, "play", "play the media", pendingIntent)
|
||||
} else {
|
||||
Log.e(TAG, "setting actions with pause button")
|
||||
actionIntent = Intent(PlayerNotificationManager.ACTION_PAUSE)
|
||||
pendingIntent =
|
||||
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, flags)
|
||||
icon = Icon.createWithResource(
|
||||
applicationContext,
|
||||
com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause
|
||||
)
|
||||
remoteAction = RemoteAction(icon, "pause", "pause the media", pendingIntent)
|
||||
}
|
||||
actions.add(remoteAction)
|
||||
|
||||
|
||||
//add custom actions to pip window
|
||||
val params = PictureInPictureParams.Builder()
|
||||
.setActions(actions)
|
||||
.build()
|
||||
setPictureInPictureParams(params)
|
||||
}
|
||||
|
||||
private fun changedToPipMode() {
|
||||
val fragmentManager = supportFragmentManager
|
||||
val videoPlayerFragment =
|
||||
(fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||
videoPlayerFragment.showControls(false)
|
||||
//create custom actions
|
||||
makePipControls()
|
||||
|
||||
//setup receiver to handle customer actions
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(PlayerNotificationManager.ACTION_STOP)
|
||||
filter.addAction(PlayerNotificationManager.ACTION_PAUSE)
|
||||
filter.addAction(PlayerNotificationManager.ACTION_PLAY)
|
||||
filter.addAction(getString(R.string.app_background_audio))
|
||||
receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action!!
|
||||
if (action == PlayerNotificationManager.ACTION_PAUSE) {
|
||||
videoPlayerFragment.pauseVideo()
|
||||
makePipControls()
|
||||
}
|
||||
if (action == PlayerNotificationManager.ACTION_PLAY) {
|
||||
videoPlayerFragment.unPauseVideo()
|
||||
makePipControls()
|
||||
}
|
||||
if (action == getString(R.string.app_background_audio)) {
|
||||
unregisterReceiver(receiver)
|
||||
finish()
|
||||
}
|
||||
if (action == PlayerNotificationManager.ACTION_STOP) {
|
||||
unregisterReceiver(receiver)
|
||||
finishAndRemoveTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
registerReceiver(receiver, filter)
|
||||
Log.v(TAG, "switched to pip ")
|
||||
floatMode = true
|
||||
videoPlayerFragment.showControls(false)
|
||||
}
|
||||
|
||||
private fun changedToNormalMode() {
|
||||
val fragmentManager = supportFragmentManager
|
||||
val videoPlayerFragment =
|
||||
(fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||
videoPlayerFragment.showControls(true)
|
||||
if (receiver != null) {
|
||||
unregisterReceiver(receiver)
|
||||
}
|
||||
Log.v(TAG, "switched to normal")
|
||||
floatMode = false
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Set theme
|
||||
val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE)
|
||||
setTheme(
|
||||
resources.getIdentifier(
|
||||
sharedPref.getString(
|
||||
getString(R.string.pref_theme_key),
|
||||
getString(R.string.app_default_theme)
|
||||
),
|
||||
"style",
|
||||
packageName
|
||||
)
|
||||
)
|
||||
setContentView(R.layout.activity_video_play)
|
||||
|
||||
// get video ID
|
||||
val intent = intent
|
||||
val videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID)
|
||||
val videoPlayerFragment =
|
||||
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||
val playingVideo = videoPlayerFragment.videoUuid
|
||||
Log.v(TAG, "oncreate click: $videoUuid is trying to replace: $playingVideo")
|
||||
when {
|
||||
TextUtils.isEmpty(playingVideo) -> {
|
||||
Log.v(TAG, "oncreate no video currently playing")
|
||||
videoPlayerFragment.start(videoUuid)
|
||||
}
|
||||
playingVideo != videoUuid -> {
|
||||
Log.v(TAG, "oncreate different video playing currently")
|
||||
videoPlayerFragment.stopVideo()
|
||||
videoPlayerFragment.start(videoUuid)
|
||||
}
|
||||
else -> {
|
||||
Log.v(TAG, "oncreate same video playing currently")
|
||||
}
|
||||
}
|
||||
|
||||
// if we are in landscape set the video to fullscreen
|
||||
val orientation = this.resources.configuration.orientation
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
setOrientation(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
val videoPlayerFragment =
|
||||
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||
val videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID)
|
||||
Log.v(
|
||||
TAG,
|
||||
"new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.videoUuid
|
||||
)
|
||||
val playingVideo = videoPlayerFragment.videoUuid
|
||||
when {
|
||||
TextUtils.isEmpty(playingVideo) -> {
|
||||
Log.v(TAG, "new intent no video currently playing")
|
||||
videoPlayerFragment.start(videoUuid)
|
||||
}
|
||||
playingVideo != videoUuid -> {
|
||||
Log.v(TAG, "new intent different video playing currently")
|
||||
videoPlayerFragment.stopVideo()
|
||||
videoPlayerFragment.start(videoUuid)
|
||||
}
|
||||
else -> {
|
||||
Log.v(TAG, "new intent same video playing currently")
|
||||
}
|
||||
}
|
||||
|
||||
// if we are in landscape set the video to fullscreen
|
||||
val orientation = this.resources.configuration.orientation
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
setOrientation(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
Log.v(TAG, "onConfigurationChanged()...")
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
// Checking the orientation changes of the screen
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
setOrientation(true)
|
||||
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
setOrientation(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setOrientation(isLandscape: Boolean) {
|
||||
val fragmentManager = supportFragmentManager
|
||||
val videoPlayerFragment =
|
||||
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||
val videoMetaFragment =
|
||||
fragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment?
|
||||
assert(videoPlayerFragment != null)
|
||||
val params = videoPlayerFragment!!.requireView().layoutParams as RelativeLayout.LayoutParams
|
||||
params.width = FrameLayout.LayoutParams.MATCH_PARENT
|
||||
params.height =
|
||||
if (isLandscape) FrameLayout.LayoutParams.MATCH_PARENT else TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
250f,
|
||||
resources.displayMetrics
|
||||
)
|
||||
.toInt()
|
||||
videoPlayerFragment.requireView().layoutParams = params
|
||||
if (videoMetaFragment != null) {
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
if (isLandscape) {
|
||||
transaction.hide(videoMetaFragment)
|
||||
} else {
|
||||
transaction.show(videoMetaFragment)
|
||||
}
|
||||
transaction.commit()
|
||||
}
|
||||
videoPlayerFragment.setIsFullscreen(isLandscape)
|
||||
if (isLandscape) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
val videoPlayerFragment =
|
||||
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||
videoPlayerFragment.destroyVideo()
|
||||
super.onDestroy()
|
||||
Log.v(TAG, "onDestroy...")
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Log.v(TAG, "onPause()...")
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Log.v(TAG, "onResume()...")
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
val videoPlayerFragment =
|
||||
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||
videoPlayerFragment.stopVideo()
|
||||
|
||||
// TODO: doesn't remove fragment??
|
||||
val fragment: Fragment? = supportFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG)
|
||||
if (fragment != null) {
|
||||
Log.v(TAG, "remove VideoDescriptionFragment")
|
||||
supportFragmentManager.beginTransaction().remove(fragment).commit()
|
||||
}
|
||||
|
||||
Log.v(TAG, "onStop()...")
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
Log.v(TAG, "onStart()...")
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public override fun onUserLeaveHint() {
|
||||
Log.v(TAG, "onUserLeaveHint()...")
|
||||
val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE)
|
||||
val fragmentManager = supportFragmentManager
|
||||
val videoPlayerFragment =
|
||||
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||
val videoMetaDataFragment =
|
||||
fragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment?
|
||||
val backgroundBehavior = sharedPref.getString(
|
||||
getString(R.string.pref_background_behavior_key),
|
||||
getString(R.string.pref_background_stop_key)
|
||||
)
|
||||
assert(videoPlayerFragment != null)
|
||||
assert(backgroundBehavior != null)
|
||||
if (videoMetaDataFragment!!.isLeaveAppExpected) {
|
||||
super.onUserLeaveHint()
|
||||
return
|
||||
}
|
||||
if (backgroundBehavior == getString(R.string.pref_background_stop_key)) {
|
||||
Log.v(TAG, "stop the video")
|
||||
videoPlayerFragment!!.pauseVideo()
|
||||
stopService(Intent(this, VideoPlayerService::class.java))
|
||||
super.onBackPressed()
|
||||
} else if (backgroundBehavior == getString(R.string.pref_background_audio_key)) {
|
||||
Log.v(TAG, "play the Audio")
|
||||
super.onBackPressed()
|
||||
} else if (backgroundBehavior == getString(R.string.pref_background_float_key)) {
|
||||
Log.v(TAG, "play in floating video")
|
||||
//canEnterPIPMode makes sure API level is high enough
|
||||
if (VideoHelper.canEnterPipMode(this)) {
|
||||
Log.v(TAG, "enabling pip")
|
||||
enterPipMode()
|
||||
} else {
|
||||
Log.v(TAG, "unable to use pip")
|
||||
}
|
||||
} else {
|
||||
// Deal with bad entries from older version
|
||||
Log.v(TAG, "No setting, fallback")
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
// @RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@SuppressLint("NewApi")
|
||||
override fun onBackPressed() {
|
||||
Log.v(TAG, "onBackPressed()...")
|
||||
val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE)
|
||||
val videoPlayerFragment =
|
||||
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||
|
||||
// copying Youtube behavior to have back button exit full screen.
|
||||
if (videoPlayerFragment.getIsFullscreen()) {
|
||||
Log.v(TAG, "exiting full screen")
|
||||
videoPlayerFragment.fullScreenToggle()
|
||||
return
|
||||
}
|
||||
// pause video if pref is enabled
|
||||
if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) {
|
||||
videoPlayerFragment.pauseVideo()
|
||||
}
|
||||
val backgroundBehavior = sharedPref.getString(
|
||||
getString(R.string.pref_background_behavior_key),
|
||||
getString(R.string.pref_background_stop_key)
|
||||
)!!
|
||||
if (backgroundBehavior == getString(R.string.pref_background_stop_key)) {
|
||||
Log.v(TAG, "stop the video")
|
||||
videoPlayerFragment.pauseVideo()
|
||||
stopService(Intent(this, VideoPlayerService::class.java))
|
||||
super.onBackPressed()
|
||||
} else if (backgroundBehavior == getString(R.string.pref_background_audio_key)) {
|
||||
Log.v(TAG, "play the Audio")
|
||||
super.onBackPressed()
|
||||
} else if (backgroundBehavior == getString(R.string.pref_background_float_key)) {
|
||||
Log.v(TAG, "play in floating video")
|
||||
//canEnterPIPMode makes sure API level is high enough
|
||||
if (VideoHelper.canEnterPipMode(this)) {
|
||||
Log.v(TAG, "enabling pip")
|
||||
enterPipMode()
|
||||
//fixes problem where back press doesn't bring up video list after returning from PIP mode
|
||||
val intentSettings = Intent(this, VideoListActivity::class.java)
|
||||
this.startActivity(intentSettings)
|
||||
} else {
|
||||
Log.v(TAG, "Unable to enter PIP mode")
|
||||
super.onBackPressed()
|
||||
}
|
||||
} else {
|
||||
// Deal with bad entries from older version
|
||||
Log.v(TAG, "No setting, fallback")
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
fun enterPipMode() {
|
||||
val fragmentManager = supportFragmentManager
|
||||
val videoPlayerFragment =
|
||||
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||
if (videoPlayerFragment!!.videoAspectRatio == 0.toFloat()) {
|
||||
Log.i(TAG, "impossible to switch to pip")
|
||||
} else {
|
||||
val rational = Rational((videoPlayerFragment.videoAspectRatio * 100).toInt(), 100)
|
||||
val mParams = PictureInPictureParams.Builder()
|
||||
.setAspectRatio(rational) // .setSourceRectHint(new Rect(0,500,400,600))
|
||||
.build()
|
||||
enterPictureInPictureMode(mParams)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(
|
||||
isInPictureInPictureMode: Boolean,
|
||||
newConfig: Configuration
|
||||
) {
|
||||
val fragmentManager = supportFragmentManager
|
||||
val videoPlayerFragment =
|
||||
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||
if (videoPlayerFragment != null) {
|
||||
if (isInPictureInPictureMode) {
|
||||
changedToPipMode()
|
||||
Log.v(TAG, "switched to pip ")
|
||||
videoPlayerFragment.useController(false)
|
||||
} else {
|
||||
changedToNormalMode()
|
||||
Log.v(TAG, "switched to normal")
|
||||
videoPlayerFragment.useController(true)
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "videoPlayerFragment is NULL")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VideoPlayActivity"
|
||||
var floatMode = false
|
||||
private const val REQUEST_CODE = 101
|
||||
}
|
||||
}
|
|
@ -94,15 +94,15 @@ public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.AccountV
|
|||
holder.videoMeta.setText(
|
||||
MetaDataHelper.getMetaString(videoList.get(position).getCreatedAt(),
|
||||
videoList.get(position).getViews(),
|
||||
context
|
||||
context,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
// set owner
|
||||
holder.videoOwner.setText(
|
||||
MetaDataHelper.getOwnerString(videoList.get(position).getAccount().getName(),
|
||||
videoList.get(position).getAccount().getHost(),
|
||||
context
|
||||
MetaDataHelper.getOwnerString(videoList.get(position).getAccount(),
|
||||
context, true
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -4,19 +4,14 @@ import android.view.LayoutInflater
|
|||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.databinding.ItemCategoryTitleBinding
|
||||
import net.schueller.peertube.databinding.ItemChannelTitleBinding
|
||||
import net.schueller.peertube.databinding.ItemTagTitleBinding
|
||||
import net.schueller.peertube.databinding.RowVideoListBinding
|
||||
import net.schueller.peertube.model.Category
|
||||
import net.schueller.peertube.model.Channel
|
||||
import net.schueller.peertube.model.TagVideo
|
||||
import net.schueller.peertube.model.Video
|
||||
import net.schueller.peertube.model.VideoList
|
||||
import net.schueller.peertube.databinding.*
|
||||
import net.schueller.peertube.fragment.VideoMetaDataFragment
|
||||
import net.schueller.peertube.model.*
|
||||
import net.schueller.peertube.model.ui.OverviewRecycleViewItem
|
||||
import net.schueller.peertube.model.ui.VideoMetaViewItem
|
||||
import java.util.ArrayList
|
||||
|
||||
class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHolder>() {
|
||||
class MultiViewRecycleViewAdapter(private val videoMetaDataFragment: VideoMetaDataFragment? = null) : RecyclerView.Adapter<MultiViewRecyclerViewHolder>() {
|
||||
|
||||
private var items = ArrayList<OverviewRecycleViewItem>()
|
||||
set(value) {
|
||||
|
@ -34,6 +29,11 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun setVideoMeta(videoMetaViewItem: VideoMetaViewItem) {
|
||||
items.add(videoMetaViewItem)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun setCategoryTitle(category: Category) {
|
||||
items.add(category)
|
||||
notifyDataSetChanged()
|
||||
|
@ -49,6 +49,11 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun setVideoComment(commentThread: CommentThread) {
|
||||
items.add(commentThread)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun clearData() {
|
||||
items.clear()
|
||||
notifyDataSetChanged()
|
||||
|
@ -83,6 +88,21 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||
false
|
||||
)
|
||||
)
|
||||
R.layout.item_video_meta -> MultiViewRecyclerViewHolder.VideoMetaViewHolder(
|
||||
ItemVideoMetaBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
),
|
||||
videoMetaDataFragment
|
||||
)
|
||||
R.layout.item_video_comments_overview -> MultiViewRecyclerViewHolder.VideoCommentsViewHolder(
|
||||
ItemVideoCommentsOverviewBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
else -> throw IllegalArgumentException("Invalid ViewType Provided")
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +113,8 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||
is MultiViewRecyclerViewHolder.CategoryViewHolder -> holder.bind(items[position] as Category)
|
||||
is MultiViewRecyclerViewHolder.ChannelViewHolder -> holder.bind(items[position] as Channel)
|
||||
is MultiViewRecyclerViewHolder.TagViewHolder -> holder.bind(items[position] as TagVideo)
|
||||
is MultiViewRecyclerViewHolder.VideoMetaViewHolder -> holder.bind(items[position] as VideoMetaViewItem)
|
||||
is MultiViewRecyclerViewHolder.VideoCommentsViewHolder -> holder.bind(items[position] as CommentThread)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +126,8 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||
is Channel -> R.layout.item_channel_title
|
||||
is Category -> R.layout.item_category_title
|
||||
is TagVideo -> R.layout.item_tag_title
|
||||
is VideoMetaViewItem -> R.layout.item_video_meta
|
||||
is CommentThread -> R.layout.item_video_comments_overview
|
||||
else -> { return 0}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,48 +16,320 @@
|
|||
*/
|
||||
package net.schueller.peertube.adapter
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.gson.JsonObject
|
||||
import com.mikepenz.iconics.Iconics.Builder
|
||||
import com.squareup.picasso.Picasso
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.R.color
|
||||
import net.schueller.peertube.R.string
|
||||
import net.schueller.peertube.R.*
|
||||
import net.schueller.peertube.activity.AccountActivity
|
||||
import net.schueller.peertube.activity.VideoListActivity
|
||||
import net.schueller.peertube.activity.VideoListActivity.Companion
|
||||
import net.schueller.peertube.activity.VideoPlayActivity
|
||||
import net.schueller.peertube.databinding.ItemCategoryTitleBinding
|
||||
import net.schueller.peertube.databinding.ItemChannelTitleBinding
|
||||
import net.schueller.peertube.databinding.RowVideoListBinding
|
||||
import net.schueller.peertube.databinding.*
|
||||
import net.schueller.peertube.fragment.VideoMetaDataFragment
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import net.schueller.peertube.helper.MetaDataHelper.getCreatorAvatar
|
||||
import net.schueller.peertube.helper.MetaDataHelper.getCreatorString
|
||||
import net.schueller.peertube.helper.MetaDataHelper.getDuration
|
||||
import net.schueller.peertube.helper.MetaDataHelper.getMetaString
|
||||
import net.schueller.peertube.helper.MetaDataHelper.getOwnerString
|
||||
import net.schueller.peertube.model.Avatar
|
||||
import net.schueller.peertube.model.Category
|
||||
import net.schueller.peertube.model.Channel
|
||||
import net.schueller.peertube.model.Video
|
||||
import com.mikepenz.iconics.Iconics.Builder
|
||||
import net.schueller.peertube.R.id
|
||||
import net.schueller.peertube.R.menu
|
||||
import net.schueller.peertube.databinding.ItemTagTitleBinding
|
||||
import net.schueller.peertube.helper.MetaDataHelper.isChannel
|
||||
import net.schueller.peertube.intents.Intents
|
||||
import net.schueller.peertube.model.TagVideo
|
||||
import net.schueller.peertube.model.*
|
||||
import net.schueller.peertube.model.ui.VideoMetaViewItem
|
||||
import net.schueller.peertube.network.GetUserService
|
||||
import net.schueller.peertube.network.GetVideoDataService
|
||||
import net.schueller.peertube.network.RetrofitInstance
|
||||
import net.schueller.peertube.network.Session
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
|
||||
sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
class CategoryViewHolder(private val binding: ItemCategoryTitleBinding): MultiViewRecyclerViewHolder(binding) {
|
||||
var videoRating: Rating? = null
|
||||
var isLeaveAppExpected = false
|
||||
|
||||
class CategoryViewHolder(private val binding: ItemCategoryTitleBinding) : MultiViewRecyclerViewHolder(binding) {
|
||||
fun bind(category: Category) {
|
||||
binding.textViewTitle.text = category.label
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelViewHolder(private val binding: ItemChannelTitleBinding): MultiViewRecyclerViewHolder(binding) {
|
||||
class VideoCommentsViewHolder(private val binding: ItemVideoCommentsOverviewBinding) : MultiViewRecyclerViewHolder(binding) {
|
||||
fun bind(commentThread: CommentThread) {
|
||||
|
||||
binding.videoCommentsTotalCount.text = commentThread.total.toString()
|
||||
|
||||
if (commentThread.comments.isNotEmpty()) {
|
||||
val highlightedComment: Comment = commentThread.comments[0]
|
||||
|
||||
// owner / creator Avatar
|
||||
val avatar = highlightedComment.account.avatar
|
||||
if (avatar != null) {
|
||||
val baseUrl = APIUrlHelper.getUrl(binding.videoHighlightedAvatar.context)
|
||||
val avatarPath = avatar.path
|
||||
Picasso.get()
|
||||
.load(baseUrl + avatarPath)
|
||||
.into(binding.videoHighlightedAvatar)
|
||||
}
|
||||
binding.videoHighlightedComment.text = highlightedComment.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class VideoMetaViewHolder(private val binding: ItemVideoMetaBinding, private val videoMetaDataFragment: VideoMetaDataFragment?) : MultiViewRecyclerViewHolder(binding) {
|
||||
fun bind(videoMetaViewItem: VideoMetaViewItem) {
|
||||
|
||||
val video = videoMetaViewItem.video
|
||||
|
||||
if (video != null && videoMetaDataFragment != null) {
|
||||
|
||||
val context = binding.avatar.context
|
||||
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||
apiBaseURL,
|
||||
APIUrlHelper.useInsecureConnection(context)
|
||||
).create(
|
||||
GetVideoDataService::class.java
|
||||
)
|
||||
val userService = RetrofitInstance.getRetrofitInstance(
|
||||
apiBaseURL,
|
||||
APIUrlHelper.useInsecureConnection(context)
|
||||
).create(
|
||||
GetUserService::class.java
|
||||
)
|
||||
|
||||
// Title
|
||||
binding.videoName.text = video.name
|
||||
binding.videoOpenDescription.setOnClickListener {
|
||||
videoMetaDataFragment.showDescriptionFragment(video)
|
||||
}
|
||||
|
||||
// Thumbs up
|
||||
binding.videoThumbsUpWrapper.setOnClickListener {
|
||||
rateVideo(true, video, context, binding)
|
||||
}
|
||||
|
||||
// Thumbs Down
|
||||
binding.videoThumbsDownWrapper.setOnClickListener {
|
||||
rateVideo(false, video, context, binding)
|
||||
}
|
||||
|
||||
// Add to playlist
|
||||
binding.videoAddToPlaylistWrapper.setOnClickListener {
|
||||
videoMetaDataFragment.saveToPlaylist(video)
|
||||
Toast.makeText(context, context.getString(string.saved_to_playlist), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
binding.videoBlockWrapper.setOnClickListener {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(string.video_feature_not_yet_implemented),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
binding.videoFlagWrapper.setOnClickListener {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(string.video_feature_not_yet_implemented),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
// video rating
|
||||
videoRating = Rating()
|
||||
videoRating!!.rating = RATING_NONE // default
|
||||
updateVideoRating(video, binding)
|
||||
|
||||
// Retrieve which rating the user gave to this video
|
||||
if (Session.getInstance().isLoggedIn) {
|
||||
val call = videoDataService.getVideoRating(video.id)
|
||||
call.enqueue(object : Callback<Rating?> {
|
||||
override fun onResponse(call: Call<Rating?>, response: Response<Rating?>) {
|
||||
videoRating = response.body()
|
||||
updateVideoRating(video, binding)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Rating?>, t: Throwable) {
|
||||
// Do nothing.
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Share
|
||||
binding.videoShare.setOnClickListener {
|
||||
isLeaveAppExpected = true
|
||||
Intents.Share(context, video)
|
||||
}
|
||||
|
||||
// hide download if not supported by video
|
||||
if (video.downloadEnabled) {
|
||||
binding.videoDownloadWrapper.setOnClickListener {
|
||||
Intents.Download(context, video)
|
||||
}
|
||||
} else {
|
||||
binding.videoDownloadWrapper.visibility = GONE
|
||||
}
|
||||
|
||||
// created at / views
|
||||
binding.videoMeta.text = getMetaString(
|
||||
video.createdAt,
|
||||
video.views,
|
||||
context,
|
||||
true
|
||||
)
|
||||
|
||||
// owner / creator
|
||||
val displayNameAndHost = getOwnerString(video.account, context)
|
||||
if (isChannel(video)) {
|
||||
binding.videoBy.text = context.resources.getString(string.video_by_line, displayNameAndHost)
|
||||
} else {
|
||||
binding.videoBy.visibility = GONE
|
||||
}
|
||||
|
||||
binding.videoOwner.text = getCreatorString(video, context)
|
||||
|
||||
// owner / creator Avatar
|
||||
val avatar = getCreatorAvatar(video, context)
|
||||
if (avatar != null) {
|
||||
val baseUrl = APIUrlHelper.getUrl(context)
|
||||
val avatarPath = avatar.path
|
||||
Picasso.get()
|
||||
.load(baseUrl + avatarPath)
|
||||
.into(binding.avatar)
|
||||
}
|
||||
|
||||
// videoOwnerSubscribers
|
||||
binding.videoOwnerSubscribers.text = context.resources.getQuantityString(R.plurals.video_channel_subscribers, video.channel.followersCount, video.channel.followersCount)
|
||||
|
||||
|
||||
// video owner click
|
||||
binding.videoCreatorInfo.setOnClickListener {
|
||||
val intent = Intent(context, AccountActivity::class.java)
|
||||
intent.putExtra(VideoListActivity.EXTRA_ACCOUNTDISPLAYNAME, displayNameAndHost)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
// avatar click
|
||||
binding.avatar.setOnClickListener {
|
||||
val intent = Intent(context, AccountActivity::class.java)
|
||||
intent.putExtra(Companion.EXTRA_ACCOUNTDISPLAYNAME, displayNameAndHost)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
|
||||
// get subscription status
|
||||
var isSubscribed = false
|
||||
|
||||
if (Session.getInstance().isLoggedIn) {
|
||||
val subChannel = video.channel.name + "@" + video.channel.host
|
||||
val call = userService.subscriptionsExist(subChannel)
|
||||
call.enqueue(object : Callback<JsonObject> {
|
||||
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
|
||||
if (response.isSuccessful) {
|
||||
// {"video.channel.name + "@" + video.channel.host":true}
|
||||
if (response.body()?.get(video.channel.name + "@" + video.channel.host)!!.asBoolean) {
|
||||
binding.videoOwnerSubscribeButton.setText(string.unsubscribe)
|
||||
isSubscribed = true
|
||||
} else {
|
||||
binding.videoOwnerSubscribeButton.setText(string.subscribe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<JsonObject>, t: Throwable) {
|
||||
// Do nothing.
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// TODO: update subscriber count
|
||||
binding.videoOwnerSubscribeButton.setOnClickListener {
|
||||
if (Session.getInstance().isLoggedIn) {
|
||||
if (!isSubscribed) {
|
||||
val payload = video.channel.name + "@" + video.channel.host
|
||||
val body = "{\"uri\":\"$payload\"}".toRequestBody("application/json".toMediaType())
|
||||
val call = userService.subscribe(body)
|
||||
call.enqueue(object : Callback<ResponseBody?> {
|
||||
override fun onResponse(
|
||||
call: Call<ResponseBody?>,
|
||||
response: Response<ResponseBody?>
|
||||
) {
|
||||
if (response.isSuccessful) {
|
||||
binding.videoOwnerSubscribeButton.setText(string.unsubscribe)
|
||||
isSubscribed = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
|
||||
// Do nothing.
|
||||
}
|
||||
})
|
||||
} else {
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(string.video_sub_del_alert_title))
|
||||
.setMessage(context.getString(string.video_sub_del_alert_msg))
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
// Yes
|
||||
val payload = video.channel.name + "@" + video.channel.host
|
||||
val call = userService.unsubscribe(payload)
|
||||
call.enqueue(object : Callback<ResponseBody?> {
|
||||
override fun onResponse(
|
||||
call: Call<ResponseBody?>,
|
||||
response: Response<ResponseBody?>
|
||||
) {
|
||||
if (response.isSuccessful) {
|
||||
binding.videoOwnerSubscribeButton.setText(string.subscribe)
|
||||
isSubscribed = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
|
||||
// Do nothing.
|
||||
}
|
||||
})
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
|
||||
// No
|
||||
}
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(string.video_login_required_for_service),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelViewHolder(private val binding: ItemChannelTitleBinding) : MultiViewRecyclerViewHolder(binding) {
|
||||
fun bind(channel: Channel) {
|
||||
|
||||
val context = binding.avatar.context
|
||||
|
@ -68,22 +340,22 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi
|
|||
if (avatar != null) {
|
||||
val avatarPath = avatar.path
|
||||
Picasso.get()
|
||||
.load(baseUrl + avatarPath)
|
||||
.placeholder(R.drawable.test_image)
|
||||
.into(binding.avatar)
|
||||
.load(baseUrl + avatarPath)
|
||||
.placeholder(R.drawable.test_image)
|
||||
.into(binding.avatar)
|
||||
}
|
||||
|
||||
binding.textViewTitle.text = channel.displayName
|
||||
}
|
||||
}
|
||||
|
||||
class TagViewHolder(private val binding: ItemTagTitleBinding): MultiViewRecyclerViewHolder(binding) {
|
||||
class TagViewHolder(private val binding: ItemTagTitleBinding) : MultiViewRecyclerViewHolder(binding) {
|
||||
fun bind(tag: TagVideo) {
|
||||
binding.textViewTitle.text = tag.tag
|
||||
}
|
||||
}
|
||||
|
||||
class VideoViewHolder(private val binding: RowVideoListBinding): MultiViewRecyclerViewHolder(binding) {
|
||||
class VideoViewHolder(private val binding: RowVideoListBinding) : MultiViewRecyclerViewHolder(binding) {
|
||||
|
||||
fun bind(video: Video) {
|
||||
|
||||
|
@ -92,18 +364,18 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi
|
|||
|
||||
// Temp Loading Image
|
||||
Picasso.get()
|
||||
.load(baseUrl + video.previewPath)
|
||||
.placeholder(R.drawable.test_image)
|
||||
.error(R.drawable.test_image)
|
||||
.into(binding.thumb)
|
||||
.load(baseUrl + video.previewPath)
|
||||
.placeholder(R.drawable.test_image)
|
||||
.error(R.drawable.test_image)
|
||||
.into(binding.thumb)
|
||||
|
||||
// Avatar
|
||||
val avatar: Avatar? = video.account.avatar
|
||||
val avatar = getCreatorAvatar(video, context)
|
||||
if (avatar != null) {
|
||||
val avatarPath = avatar.path
|
||||
Picasso.get()
|
||||
.load(baseUrl + avatarPath)
|
||||
.into(binding.avatar)
|
||||
.load(baseUrl + avatarPath)
|
||||
.into(binding.avatar)
|
||||
}
|
||||
// set Name
|
||||
binding.slRowName.text = video.name
|
||||
|
@ -119,18 +391,14 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi
|
|||
|
||||
// set age and view count
|
||||
binding.videoMeta.text = getMetaString(
|
||||
video.createdAt,
|
||||
video.views,
|
||||
context
|
||||
video.createdAt,
|
||||
video.views,
|
||||
context
|
||||
)
|
||||
|
||||
// set owner
|
||||
val displayNameAndHost = getOwnerString(
|
||||
video.account.name,
|
||||
video.account.host,
|
||||
context
|
||||
)
|
||||
binding.videoOwner.text = displayNameAndHost
|
||||
val displayNameAndHost = getOwnerString(video.account, context, true)
|
||||
binding.videoOwner.text = getCreatorString(video, context, true)
|
||||
|
||||
// video owner click
|
||||
binding.videoOwner.setOnClickListener {
|
||||
|
@ -159,8 +427,8 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi
|
|||
|
||||
binding.moreButton.setOnClickListener { v: View? ->
|
||||
val popup = PopupMenu(
|
||||
context,
|
||||
v!!
|
||||
context,
|
||||
v!!
|
||||
)
|
||||
popup.setOnMenuItemClickListener { menuItem: MenuItem ->
|
||||
when (menuItem.itemId) {
|
||||
|
@ -178,4 +446,117 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi
|
|||
}
|
||||
|
||||
|
||||
fun updateVideoRating(video: Video?, binding: ItemVideoMetaBinding) {
|
||||
|
||||
when (videoRating!!.rating) {
|
||||
RATING_NONE -> {
|
||||
Log.v("MWCVH", "RATING_NONE")
|
||||
binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up)
|
||||
binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down)
|
||||
}
|
||||
RATING_LIKE -> {
|
||||
Log.v("MWCVH", "RATING_LIKE")
|
||||
binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up_filled)
|
||||
binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down)
|
||||
}
|
||||
RATING_DISLIKE -> {
|
||||
Log.v("MWCVH", "RATING_DISLIKE")
|
||||
binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up)
|
||||
binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down_filled)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the texts
|
||||
binding.videoThumbsUpTotal.text = video?.likes.toString()
|
||||
binding.videoThumbsDownTotal.text = video?.dislikes.toString()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: move this out and get update when rating changes
|
||||
*/
|
||||
fun rateVideo(like: Boolean, video: Video, context: Context, binding: ItemVideoMetaBinding) {
|
||||
if (Session.getInstance().isLoggedIn) {
|
||||
val ratePayload: String = when (videoRating!!.rating) {
|
||||
RATING_LIKE -> if (like) RATING_NONE else RATING_DISLIKE
|
||||
RATING_DISLIKE -> if (like) RATING_LIKE else RATING_NONE
|
||||
RATING_NONE -> if (like) RATING_LIKE else RATING_DISLIKE
|
||||
else -> if (like) RATING_LIKE else RATING_DISLIKE
|
||||
}
|
||||
|
||||
val body = "{\"rating\":\"$ratePayload\"}".toRequestBody("application/json".toMediaType())
|
||||
|
||||
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||
apiBaseURL, APIUrlHelper.useInsecureConnection(
|
||||
context
|
||||
)
|
||||
).create(
|
||||
GetVideoDataService::class.java
|
||||
)
|
||||
val call = videoDataService.rateVideo(video.id, body)
|
||||
call.enqueue(object : Callback<ResponseBody?> {
|
||||
override fun onResponse(
|
||||
call: Call<ResponseBody?>,
|
||||
response: Response<ResponseBody?>
|
||||
) {
|
||||
// if 20x, update likes/dislikes
|
||||
if (response.isSuccessful) {
|
||||
val previousRating = videoRating!!.rating
|
||||
|
||||
// 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 != ratePayload) {
|
||||
when (previousRating) {
|
||||
RATING_NONE -> if (ratePayload == RATING_LIKE) {
|
||||
video.likes = video.likes + 1
|
||||
} else {
|
||||
video.dislikes = video.dislikes + 1
|
||||
}
|
||||
RATING_LIKE -> {
|
||||
video.likes = video.likes - 1
|
||||
if (ratePayload == RATING_DISLIKE) {
|
||||
video.dislikes = video.dislikes + 1
|
||||
}
|
||||
}
|
||||
RATING_DISLIKE -> {
|
||||
video.dislikes = video.dislikes - 1
|
||||
if (ratePayload == RATING_LIKE) {
|
||||
video.likes = video.likes + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
videoRating!!.rating = ratePayload
|
||||
updateVideoRating(video, binding)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(string.video_rating_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(string.video_login_required_for_service),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RATING_NONE = "none"
|
||||
private const val RATING_LIKE = "like"
|
||||
private const val RATING_DISLIKE = "dislike"
|
||||
const val EXTRA_VIDEOID = "VIDEOID"
|
||||
const val EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.database.Video
|
||||
import net.schueller.peertube.databinding.RowPlaylistBinding
|
||||
|
||||
class PlaylistAdapter(private val mVideos: MutableList<Video>, private val onClick: (Video) -> Unit) : RecyclerView.Adapter<PlaylistAdapter.VideoViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoViewHolder {
|
||||
|
||||
val binding = RowPlaylistBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
|
||||
return VideoViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VideoViewHolder, position: Int) {
|
||||
holder.bind(mVideos[position])
|
||||
}
|
||||
|
||||
fun setVideos(videos: List<Video>) {
|
||||
mVideos.clear()
|
||||
mVideos.addAll(videos)
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mVideos.size
|
||||
}
|
||||
|
||||
inner class VideoViewHolder(private val binding: RowPlaylistBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(video: Video) {
|
||||
|
||||
binding.videoName.text = video.videoName
|
||||
binding.videoDescription.text = video.videoDescription
|
||||
|
||||
binding.root.setOnClickListener { onClick(video) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getVideoAtPosition(position: Int): Video {
|
||||
return mVideos[position]
|
||||
}
|
||||
}
|
|
@ -19,7 +19,9 @@ package net.schueller.peertube.database;
|
|||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
@Database(entities = {Server.class}, version = 1)
|
||||
@Database(entities = {Server.class, Video.class}, version = 1)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract ServerDao serverDao();
|
||||
|
||||
public abstract VideoDao videoDao();
|
||||
}
|
|
@ -20,7 +20,7 @@ import android.os.Parcelable
|
|||
import androidx.room.PrimaryKey
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "server_table")
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
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
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package net.schueller.peertube.database
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "watch_later")
|
||||
data class Video(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Int = 0,
|
||||
|
||||
@ColumnInfo(name = "video_uuid")
|
||||
var videoUUID: String,
|
||||
|
||||
@ColumnInfo(name = "video_name")
|
||||
var videoName: String,
|
||||
|
||||
@ColumnInfo(name = "video_description")
|
||||
var videoDescription: String?
|
||||
|
||||
) : Parcelable
|
|
@ -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 VideoDao {
|
||||
|
||||
@Insert
|
||||
suspend fun insert(video: Video)
|
||||
|
||||
@Update
|
||||
suspend fun update(video: Video)
|
||||
|
||||
@Query("DELETE FROM watch_later")
|
||||
suspend fun deleteAll()
|
||||
|
||||
@Delete
|
||||
suspend fun delete(video: Video)
|
||||
|
||||
@get:Query("SELECT * from watch_later ORDER BY video_name DESC")
|
||||
val allVideos: LiveData<List<Video>>
|
||||
}
|
|
@ -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.LiveData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class VideoRepository(application: Application) {
|
||||
|
||||
private val mVideoDao: VideoDao
|
||||
|
||||
val allVideos: LiveData<List<Video>>
|
||||
get() = mVideoDao.allVideos
|
||||
|
||||
init {
|
||||
val db = VideoRoomDatabase.getDatabase(application)
|
||||
mVideoDao = db.videoDao()
|
||||
}
|
||||
|
||||
suspend fun update(video: Video) = withContext(Dispatchers.IO) {
|
||||
mVideoDao.update(video)
|
||||
}
|
||||
|
||||
suspend fun insert(video: Video) = withContext(Dispatchers.IO) {
|
||||
mVideoDao.insert(video)
|
||||
}
|
||||
|
||||
suspend fun delete(video: Video) = withContext(Dispatchers.IO) {
|
||||
mVideoDao.delete(video)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.content.Context;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Database(entities = {Video.class}, version = 1, exportSchema = false)
|
||||
public abstract class VideoRoomDatabase extends RoomDatabase {
|
||||
|
||||
public abstract VideoDao videoDao();
|
||||
|
||||
private static volatile VideoRoomDatabase INSTANCE;
|
||||
|
||||
private static final int NUMBER_OF_THREADS = 4;
|
||||
|
||||
static final ExecutorService databaseWriteExecutor =
|
||||
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
|
||||
|
||||
public static VideoRoomDatabase getDatabase(final Context context) {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (VideoRoomDatabase.class) {
|
||||
if (INSTANCE == null) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
||||
VideoRoomDatabase.class, "playlist_database")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 VideoViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val mRepository: VideoRepository = VideoRepository(application)
|
||||
val allVideos: LiveData<List<Video>> = mRepository.allVideos
|
||||
|
||||
fun insert(video: Video) {
|
||||
viewModelScope.launch {
|
||||
mRepository.insert(video)
|
||||
}
|
||||
}
|
||||
|
||||
fun update(video: Video) {
|
||||
viewModelScope.launch {
|
||||
mRepository.update(video)
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(video: Video) {
|
||||
viewModelScope.launch {
|
||||
mRepository.delete(video)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,12 +19,13 @@ 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.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import net.schueller.peertube.R
|
||||
|
@ -52,7 +53,7 @@ class AddServerFragment : Fragment() {
|
|||
}
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
mBinding = FragmentAddServerBinding.inflate(inflater, container, false)
|
||||
return mBinding.root
|
||||
}
|
||||
|
@ -115,7 +116,7 @@ class AddServerFragment : Fragment() {
|
|||
|
||||
mBinding.pickServerUrl.setOnClickListener {
|
||||
val intentServer = Intent(activity, SearchServerActivity::class.java)
|
||||
this.startActivityForResult(intentServer, PICK_SERVER)
|
||||
openActivityForResult(intentServer)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,35 +133,24 @@ class AddServerFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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"))
|
||||
private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val intent = result.data
|
||||
val serverUrlTest = intent?.getStringExtra("serverUrl")
|
||||
mBinding.serverUrl.setText(serverUrlTest)
|
||||
mBinding.serverLabel.apply {
|
||||
if (text.toString().isBlank()) {
|
||||
setText(intent?.getStringExtra("serverName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openActivityForResult(intent: Intent) {
|
||||
resultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import net.schueller.peertube.R
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import net.schueller.peertube.helper.ErrorHelper
|
||||
import net.schueller.peertube.model.Description
|
||||
import net.schueller.peertube.model.Video
|
||||
import net.schueller.peertube.network.GetVideoDataService
|
||||
import net.schueller.peertube.network.RetrofitInstance
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class VideoDescriptionFragment : Fragment () {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_video_description, container,
|
||||
false
|
||||
)
|
||||
|
||||
val video = video
|
||||
|
||||
if (video != null) {
|
||||
|
||||
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||
apiBaseURL,
|
||||
APIUrlHelper.useInsecureConnection(context)
|
||||
).create(
|
||||
GetVideoDataService::class.java
|
||||
)
|
||||
|
||||
// description, get extended if available
|
||||
val videoDescription = view.findViewById<TextView>(R.id.description)
|
||||
val shortDescription = video.description
|
||||
if (shortDescription != null && shortDescription.length > 237) {
|
||||
val call = videoDataService.getVideoFullDescription(video.uuid);
|
||||
call.enqueue(object : Callback<Description?> {
|
||||
override fun onResponse(call: Call<Description?>, response: Response<Description?>) {
|
||||
val videoFullDescription: Description? = response.body();
|
||||
|
||||
videoDescription.text = videoFullDescription?.description
|
||||
}
|
||||
override fun onFailure(call: Call<Description?>, t: Throwable) {
|
||||
Log.wtf(TAG, t.fillInStackTrace())
|
||||
ErrorHelper.showToastFromCommunicationError(activity, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
videoDescription.text = shortDescription;
|
||||
|
||||
val closeButton = view.findViewById<ImageButton>(R.id.video_description_close_button)
|
||||
closeButton.setOnClickListener {
|
||||
videoMetaDataFragment!!.hideDescriptionFragment()
|
||||
}
|
||||
|
||||
// video privacy
|
||||
val videoPrivacy = view.findViewById<TextView>(R.id.video_privacy);
|
||||
videoPrivacy.text = video!!.privacy.label;
|
||||
|
||||
// video category
|
||||
val videoCategory = view.findViewById<TextView>(R.id.video_category);
|
||||
videoCategory.text = video!!.category.label;
|
||||
|
||||
// video privacy
|
||||
val videoLicense = view.findViewById<TextView>(R.id.video_license);
|
||||
videoLicense.text = video!!.licence.label;
|
||||
|
||||
// video language
|
||||
val videoLanguage = view.findViewById<TextView>(R.id.video_language);
|
||||
videoLanguage.text = video!!.language.label;
|
||||
|
||||
// video privacy
|
||||
val videoTags = view.findViewById<TextView>(R.id.video_tags);
|
||||
videoTags.text = android.text.TextUtils.join(", ", video!!.tags);
|
||||
}
|
||||
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var video: Video? = null
|
||||
private var videoMetaDataFragment: VideoMetaDataFragment? = null
|
||||
const val TAG = "VideoDescr"
|
||||
fun newInstance(mVideo: Video?, mVideoMetaDataFragment: VideoMetaDataFragment): VideoDescriptionFragment {
|
||||
video = mVideo
|
||||
videoMetaDataFragment = mVideoMetaDataFragment
|
||||
return VideoDescriptionFragment()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,408 +0,0 @@
|
|||
/*
|
||||
* 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.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.mikepenz.iconics.Iconics;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.util.Objects;
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.helper.ErrorHelper;
|
||||
import net.schueller.peertube.helper.MetaDataHelper;
|
||||
import net.schueller.peertube.intents.Intents;
|
||||
import net.schueller.peertube.model.Account;
|
||||
import net.schueller.peertube.model.Avatar;
|
||||
import net.schueller.peertube.model.Description;
|
||||
import net.schueller.peertube.model.Rating;
|
||||
import net.schueller.peertube.model.Video;
|
||||
import net.schueller.peertube.network.GetVideoDataService;
|
||||
import net.schueller.peertube.network.RetrofitInstance;
|
||||
import net.schueller.peertube.network.Session;
|
||||
import net.schueller.peertube.service.VideoPlayerService;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class VideoMetaDataFragment extends Fragment {
|
||||
|
||||
private static final String TAG = "VideoMetaDataFragment";
|
||||
|
||||
private static final String RATING_NONE = "none";
|
||||
private static final String RATING_LIKE = "like";
|
||||
private static final String RATING_DISLIKE = "dislike";
|
||||
|
||||
private Rating videoRating;
|
||||
private ColorStateList defaultTextColor;
|
||||
|
||||
private boolean leaveAppExpected = false;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_video_meta, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
leaveAppExpected = false;
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
public boolean isLeaveAppExpected()
|
||||
{
|
||||
return leaveAppExpected;
|
||||
}
|
||||
|
||||
public void updateVideoMeta(Video video, VideoPlayerService mService) {
|
||||
Context context = getContext();
|
||||
Activity activity = getActivity();
|
||||
|
||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
|
||||
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class);
|
||||
|
||||
// Thumbs up
|
||||
Button thumbsUpButton = activity.findViewById(R.id.video_thumbs_up);
|
||||
defaultTextColor = thumbsUpButton.getTextColors();
|
||||
thumbsUpButton.setText(R.string.video_thumbs_up_icon);
|
||||
new Iconics.Builder().on(thumbsUpButton).build();
|
||||
thumbsUpButton.setOnClickListener(v -> {
|
||||
rateVideo(true, video);
|
||||
});
|
||||
|
||||
// Thumbs Down
|
||||
Button thumbsDownButton = activity.findViewById(R.id.video_thumbs_down);
|
||||
thumbsDownButton.setText(R.string.video_thumbs_down_icon);
|
||||
new Iconics.Builder().on(thumbsDownButton).build();
|
||||
thumbsDownButton.setOnClickListener(v -> {
|
||||
rateVideo(false, video);
|
||||
});
|
||||
|
||||
// video rating
|
||||
videoRating = new Rating();
|
||||
videoRating.setRating(RATING_NONE); // default
|
||||
updateVideoRating(video);
|
||||
|
||||
// Retrieve which rating the user gave to this video
|
||||
if (Session.getInstance().isLoggedIn()) {
|
||||
Call<Rating> call = videoDataService.getVideoRating(video.getId());
|
||||
call.enqueue(new Callback<Rating>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(Call<Rating> call, Response<Rating> response) {
|
||||
videoRating = response.body();
|
||||
updateVideoRating(video);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<Rating> call, Throwable t) {
|
||||
ErrorHelper.showToastFromCommunicationError( getActivity(), t );
|
||||
// Do nothing.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Share
|
||||
Button videoShareButton = activity.findViewById(R.id.video_share);
|
||||
videoShareButton.setText(R.string.video_share_icon);
|
||||
new Iconics.Builder().on(videoShareButton).build();
|
||||
videoShareButton.setOnClickListener(v ->
|
||||
{
|
||||
leaveAppExpected = true;
|
||||
Intents.Share( context, video );
|
||||
} );
|
||||
|
||||
// Download
|
||||
Button videoDownloadButton = activity.findViewById(R.id.video_download);
|
||||
videoDownloadButton.setText(R.string.video_download_icon);
|
||||
new Iconics.Builder().on(videoDownloadButton).build();
|
||||
videoDownloadButton.setOnClickListener(v -> {
|
||||
// get permission to store file
|
||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
leaveAppExpected = true;
|
||||
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
Intents.Download(context, video);
|
||||
} else {
|
||||
Toast.makeText(context, getString(R.string.video_download_permission_error), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
} else {
|
||||
Intents.Download(context, video);
|
||||
}
|
||||
});
|
||||
|
||||
Account account = video.getAccount();
|
||||
|
||||
// owner / creator Avatar
|
||||
Avatar avatar = account.getAvatar();
|
||||
if (avatar != null) {
|
||||
ImageView avatarView = activity.findViewById(R.id.avatar);
|
||||
String baseUrl = APIUrlHelper.getUrl(context);
|
||||
String avatarPath = avatar.getPath();
|
||||
Picasso.get()
|
||||
.load(baseUrl + avatarPath)
|
||||
.into(avatarView);
|
||||
}
|
||||
|
||||
|
||||
// title / name
|
||||
TextView videoName = activity.findViewById(R.id.sl_row_name);
|
||||
videoName.setText(video.getName());
|
||||
|
||||
// created at / views
|
||||
TextView videoMeta = activity.findViewById(R.id.videoMeta);
|
||||
videoMeta.setText(
|
||||
MetaDataHelper.getMetaString(
|
||||
video.getCreatedAt(),
|
||||
video.getViews(),
|
||||
context
|
||||
)
|
||||
);
|
||||
|
||||
// owner / creator
|
||||
TextView videoOwner = activity.findViewById(R.id.videoOwner);
|
||||
videoOwner.setText(
|
||||
MetaDataHelper.getOwnerString(video.getAccount().getName(),
|
||||
video.getAccount().getHost(),
|
||||
context
|
||||
)
|
||||
);
|
||||
|
||||
// description
|
||||
TextView videoDescription = activity.findViewById(R.id.description);
|
||||
String shortDescription = video.getDescription();
|
||||
if (shortDescription != null && Objects.requireNonNull(shortDescription).length() > 237) {
|
||||
shortDescription += "\n" + getString(R.string.video_description_read_more);
|
||||
videoDescription.setOnClickListener(v -> {
|
||||
Call<Description> call = videoDataService.getVideoFullDescription(video.getUuid());
|
||||
call.enqueue(new Callback<Description>() {
|
||||
@Override
|
||||
public void onResponse(Call<Description> call, Response<Description> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
new Description();
|
||||
Description videoFullDescription;
|
||||
videoFullDescription = response.body();
|
||||
videoDescription.setText(videoFullDescription.getDescription());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Call<Description> call, Throwable t) {
|
||||
Toast.makeText(getContext(), getString(R.string.video_get_full_description_failed), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
videoDescription.setText(shortDescription);
|
||||
|
||||
// video privacy
|
||||
TextView videoPrivacy = activity.findViewById(R.id.video_privacy);
|
||||
videoPrivacy.setText(video.getPrivacy().getLabel());
|
||||
|
||||
// video category
|
||||
TextView videoCategory = activity.findViewById(R.id.video_category);
|
||||
videoCategory.setText(video.getCategory().getLabel());
|
||||
|
||||
// video privacy
|
||||
TextView videoLicense = activity.findViewById(R.id.video_license);
|
||||
videoLicense.setText(video.getLicence().getLabel());
|
||||
|
||||
// video language
|
||||
TextView videoLanguage = activity.findViewById(R.id.video_language);
|
||||
videoLanguage.setText(video.getLanguage().getLabel());
|
||||
|
||||
// video privacy
|
||||
TextView videoTags = activity.findViewById(R.id.video_tags);
|
||||
videoTags.setText(android.text.TextUtils.join(", ", video.getTags()));
|
||||
|
||||
// more button
|
||||
TextView moreButton = activity.findViewById(R.id.moreButton);
|
||||
moreButton.setText(R.string.video_more_icon);
|
||||
new Iconics.Builder().on(moreButton).build();
|
||||
|
||||
moreButton.setOnClickListener(v -> {
|
||||
PopupMenu popup = new PopupMenu(context, v);
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.video_more_report:
|
||||
Log.v(TAG, "Report");
|
||||
Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
case R.id.video_more_blacklist:
|
||||
Log.v(TAG, "Blacklist");
|
||||
Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
popup.inflate(R.menu.menu_video_more);
|
||||
popup.show();
|
||||
});
|
||||
|
||||
// video player options
|
||||
TextView videoOptions = activity.findViewById(R.id.exo_more);
|
||||
videoOptions.setText(R.string.video_more_icon);
|
||||
new Iconics.Builder().on(videoOptions).build();
|
||||
|
||||
videoOptions.setOnClickListener(v -> {
|
||||
VideoOptionsFragment videoOptionsFragment =
|
||||
VideoOptionsFragment.newInstance(mService, video.getFiles());
|
||||
videoOptionsFragment.show(getActivity().getSupportFragmentManager(),
|
||||
VideoOptionsFragment.TAG);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void updateVideoRating(Video video) {
|
||||
Button thumbsUpButton = getActivity().findViewById(R.id.video_thumbs_up);
|
||||
Button thumbsDownButton = getActivity().findViewById(R.id.video_thumbs_down);
|
||||
|
||||
TypedValue typedValue = new TypedValue();
|
||||
|
||||
TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorPrimary});
|
||||
int accentColor = a.getColor(0, 0);
|
||||
|
||||
// Change the color of the thumbs
|
||||
switch (videoRating.getRating()) {
|
||||
case RATING_NONE:
|
||||
thumbsUpButton.setTextColor(defaultTextColor);
|
||||
thumbsDownButton.setTextColor(defaultTextColor);
|
||||
break;
|
||||
case RATING_LIKE:
|
||||
thumbsUpButton.setTextColor(accentColor);
|
||||
thumbsDownButton.setTextColor(defaultTextColor);
|
||||
break;
|
||||
case RATING_DISLIKE:
|
||||
thumbsUpButton.setTextColor(defaultTextColor);
|
||||
thumbsDownButton.setTextColor(accentColor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the texts
|
||||
TextView thumbsDownTotal = getActivity().findViewById(R.id.video_thumbs_down_total);
|
||||
TextView thumbsUpTotal = getActivity().findViewById(R.id.video_thumbs_up_total);
|
||||
thumbsUpTotal.setText(String.valueOf(video.getLikes()));
|
||||
thumbsDownTotal.setText(String.valueOf(video.getDislikes()));
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
void rateVideo(Boolean like, Video video) {
|
||||
if (Session.getInstance().isLoggedIn()) {
|
||||
final String ratePayload;
|
||||
|
||||
switch (videoRating.getRating()) {
|
||||
case RATING_LIKE:
|
||||
ratePayload = like ? RATING_NONE : RATING_DISLIKE;
|
||||
break;
|
||||
case RATING_DISLIKE:
|
||||
ratePayload = like ? RATING_LIKE : RATING_NONE;
|
||||
break;
|
||||
case RATING_NONE:
|
||||
default:
|
||||
ratePayload = like ? RATING_LIKE : RATING_DISLIKE;
|
||||
break;
|
||||
}
|
||||
|
||||
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"), "{\"rating\":\"" + ratePayload + "\"}");
|
||||
|
||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(getContext());
|
||||
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(getContext())).create(GetVideoDataService.class);
|
||||
|
||||
Call<ResponseBody> call = videoDataService.rateVideo(video.getId(), body);
|
||||
|
||||
call.enqueue(new Callback<ResponseBody>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
|
||||
//Log.v(TAG, response.toString());
|
||||
|
||||
// if 20x, update likes/dislikes
|
||||
if (response.isSuccessful()) {
|
||||
String previousRating = videoRating.getRating();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
Toast.makeText(getContext(), getString(R.string.video_rating_failed), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Toast.makeText(getContext(), getString(R.string.video_login_required_for_service), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* 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.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mikepenz.iconics.Iconics
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.adapter.MultiViewRecycleViewAdapter
|
||||
import net.schueller.peertube.database.VideoViewModel
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import net.schueller.peertube.helper.ErrorHelper
|
||||
import net.schueller.peertube.model.CommentThread
|
||||
import net.schueller.peertube.model.Rating
|
||||
import net.schueller.peertube.model.Video
|
||||
import net.schueller.peertube.model.VideoList
|
||||
import net.schueller.peertube.model.ui.VideoMetaViewItem
|
||||
import net.schueller.peertube.network.GetVideoDataService
|
||||
import net.schueller.peertube.network.RetrofitInstance
|
||||
import net.schueller.peertube.service.VideoPlayerService
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class VideoMetaDataFragment : Fragment() {
|
||||
private var videoRating: Rating? = null
|
||||
private var defaultTextColor: ColorStateList? = null
|
||||
private var recyclerView: RecyclerView? = null
|
||||
private var mMultiViewAdapter: MultiViewRecycleViewAdapter? = null
|
||||
|
||||
private lateinit var videoDescriptionFragment: VideoDescriptionFragment
|
||||
|
||||
private val mVideoViewModel: VideoViewModel by activityViewModels()
|
||||
|
||||
var isLeaveAppExpected = false
|
||||
private set
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_video_meta, container, false)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
isLeaveAppExpected = false
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
fun showDescriptionFragment(video: Video) {
|
||||
// show full description fragment
|
||||
videoDescriptionFragment = VideoDescriptionFragment.newInstance(video, this)
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(R.id.video_meta_data_fragment, videoDescriptionFragment, VideoDescriptionFragment.TAG).commit()
|
||||
}
|
||||
|
||||
fun hideDescriptionFragment() {
|
||||
val fragment: Fragment? = childFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG)
|
||||
if (fragment != null) {
|
||||
childFragmentManager.beginTransaction().remove(fragment).commit()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateVideoMeta(video: Video, mService: VideoPlayerService?) {
|
||||
|
||||
// Remove description if it is open as we are loading a new video
|
||||
hideDescriptionFragment()
|
||||
|
||||
val context = context
|
||||
val activity: Activity? = activity
|
||||
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||
apiBaseURL,
|
||||
APIUrlHelper.useInsecureConnection(context)
|
||||
).create(
|
||||
GetVideoDataService::class.java
|
||||
)
|
||||
|
||||
// related videos
|
||||
recyclerView = activity!!.findViewById(R.id.relatedVideosView)
|
||||
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this@VideoMetaDataFragment.context)
|
||||
recyclerView?.layoutManager = layoutManager
|
||||
mMultiViewAdapter = MultiViewRecycleViewAdapter(this)
|
||||
recyclerView?.adapter = mMultiViewAdapter
|
||||
|
||||
val videoMetaViewItem = VideoMetaViewItem()
|
||||
videoMetaViewItem.video = video
|
||||
mMultiViewAdapter?.setVideoMeta(videoMetaViewItem)
|
||||
|
||||
loadVideos()
|
||||
|
||||
// loadComments(video.id)
|
||||
|
||||
// mMultiViewAdapter?.setVideoComment()
|
||||
|
||||
// videoOwnerSubscribeButton
|
||||
|
||||
|
||||
// description
|
||||
|
||||
|
||||
// video player options
|
||||
val videoOptions = activity.findViewById<TextView>(R.id.exo_more)
|
||||
videoOptions.setText(R.string.video_more_icon)
|
||||
Iconics.Builder().on(videoOptions).build()
|
||||
videoOptions.setOnClickListener {
|
||||
val videoOptionsFragment = VideoOptionsFragment.newInstance(mService, video.files)
|
||||
videoOptionsFragment.show(
|
||||
getActivity()!!.supportFragmentManager,
|
||||
VideoOptionsFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadComments(videoId: Int) {
|
||||
val context = context
|
||||
|
||||
val start = 0
|
||||
val count = 1
|
||||
val sort = "-createdAt"
|
||||
|
||||
|
||||
// We set this to default to null so that on initial start there are videos listed.
|
||||
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||
val service =
|
||||
RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(
|
||||
GetVideoDataService::class.java
|
||||
)
|
||||
val call: Call<CommentThread> = service.getCommentThreads(videoId, start, count, sort)
|
||||
|
||||
call.enqueue(object : Callback<CommentThread?> {
|
||||
override fun onResponse(call: Call<CommentThread?>, response: Response<CommentThread?>) {
|
||||
if (response.body() != null) {
|
||||
val commentThread = response.body()
|
||||
if (commentThread != null) {
|
||||
mMultiViewAdapter!!.setVideoComment(commentThread)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<CommentThread?>, t: Throwable) {
|
||||
Log.wtf("err", t.fillInStackTrace())
|
||||
ErrorHelper.showToastFromCommunicationError(this@VideoMetaDataFragment.context, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun loadVideos() {
|
||||
val context = context
|
||||
|
||||
val start = 0
|
||||
val count = 6
|
||||
val sort = "-createdAt"
|
||||
val filter: String? = null
|
||||
|
||||
val sharedPref = context?.getSharedPreferences(
|
||||
context.packageName + "_preferences",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
var nsfw = "false"
|
||||
var languages: Set<String>? = emptySet()
|
||||
if (sharedPref != null) {
|
||||
nsfw = if (sharedPref.getBoolean(getString(R.string.pref_show_nsfw_key), false)) "both" else "false"
|
||||
languages = sharedPref.getStringSet(getString(R.string.pref_video_language_key), null)
|
||||
}
|
||||
|
||||
// We set this to default to null so that on initial start there are videos listed.
|
||||
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||
val service =
|
||||
RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(
|
||||
GetVideoDataService::class.java
|
||||
)
|
||||
val call: Call<VideoList> = service.getVideosData(start, count, sort, nsfw, filter, languages)
|
||||
|
||||
/*Log the URL called*/Log.d("URL Called", call.request().url.toString() + "")
|
||||
// Toast.makeText(VideoListActivity.this, "URL Called: " + call.request().url(), Toast.LENGTH_SHORT).show();
|
||||
call.enqueue(object : Callback<VideoList?> {
|
||||
override fun onResponse(call: Call<VideoList?>, response: Response<VideoList?>) {
|
||||
if (response.body() != null) {
|
||||
val videoList = response.body()
|
||||
if (videoList != null) {
|
||||
mMultiViewAdapter!!.setVideoListData(videoList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<VideoList?>, t: Throwable) {
|
||||
Log.wtf("err", t.fillInStackTrace())
|
||||
ErrorHelper.showToastFromCommunicationError(this@VideoMetaDataFragment.context, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun saveToPlaylist(video: Video) {
|
||||
val playlistVideo: net.schueller.peertube.database.Video = net.schueller.peertube.database.Video(videoUUID = video.uuid, videoName = video.name, videoDescription = video.description)
|
||||
mVideoViewModel.insert(playlistVideo)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "VMDF"
|
||||
}
|
||||
}
|
|
@ -1,516 +0,0 @@
|
|||
/*
|
||||
* 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.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.se_bastiaan.torrentstream.StreamStatus;
|
||||
import com.github.se_bastiaan.torrentstream.Torrent;
|
||||
import com.github.se_bastiaan.torrentstream.TorrentOptions;
|
||||
import com.github.se_bastiaan.torrentstream.TorrentStream;
|
||||
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
import com.mikepenz.iconics.Iconics;
|
||||
|
||||
import net.schueller.peertube.R;
|
||||
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.helper.ErrorHelper;
|
||||
import net.schueller.peertube.model.File;
|
||||
import net.schueller.peertube.model.Video;
|
||||
import net.schueller.peertube.network.GetVideoDataService;
|
||||
import net.schueller.peertube.network.RetrofitInstance;
|
||||
import net.schueller.peertube.service.VideoPlayerService;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode;
|
||||
|
||||
public class VideoPlayerFragment extends Fragment implements VideoRendererEventListener {
|
||||
|
||||
private String mVideoUuid;
|
||||
private ProgressBar progressBar;
|
||||
private PlayerView simpleExoPlayerView;
|
||||
private Intent videoPlayerIntent;
|
||||
private Boolean mBound = false;
|
||||
private Boolean isFullscreen = false;
|
||||
private VideoPlayerService mService;
|
||||
private TorrentStream torrentStream;
|
||||
private LinearLayout torrentStatus;
|
||||
private float aspectRatio;
|
||||
|
||||
private static final String TAG = "VideoPlayerFragment";
|
||||
private GestureDetector mDetector;
|
||||
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.d(TAG, "onServiceConnected");
|
||||
VideoPlayerService.LocalBinder binder = (VideoPlayerService.LocalBinder) service;
|
||||
mService = binder.getService();
|
||||
|
||||
// 2. Create the player
|
||||
simpleExoPlayerView.setPlayer(mService.player);
|
||||
mBound = true;
|
||||
|
||||
loadVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
Log.d(TAG, "onServiceDisconnected");
|
||||
simpleExoPlayerView.setPlayer(null);
|
||||
mBound = false;
|
||||
}
|
||||
};
|
||||
private AspectRatioFrameLayout.AspectRatioListener aspectRatioListerner = new AspectRatioFrameLayout.AspectRatioListener()
|
||||
{
|
||||
@Override
|
||||
public void onAspectRatioUpdated( float targetAspectRatio, float naturalAspectRatio, boolean aspectRatioMismatch )
|
||||
{
|
||||
aspectRatio = targetAspectRatio;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_video_player, container, false);
|
||||
}
|
||||
|
||||
|
||||
public void start(String videoUuid) {
|
||||
|
||||
// start service
|
||||
Context context = getContext();
|
||||
Activity activity = getActivity();
|
||||
|
||||
mVideoUuid = videoUuid;
|
||||
|
||||
assert activity != null;
|
||||
progressBar = activity.findViewById(R.id.torrent_progress);
|
||||
progressBar.setMax(100);
|
||||
|
||||
assert context != null;
|
||||
simpleExoPlayerView = new PlayerView(context);
|
||||
simpleExoPlayerView = activity.findViewById(R.id.video_view);
|
||||
|
||||
simpleExoPlayerView.setControllerShowTimeoutMs(1000);
|
||||
simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
|
||||
|
||||
mDetector = new GestureDetector(context, new MyGestureListener());
|
||||
simpleExoPlayerView.setOnTouchListener(touchListener);
|
||||
|
||||
simpleExoPlayerView.setAspectRatioListener( aspectRatioListerner );
|
||||
|
||||
torrentStatus = activity.findViewById(R.id.exo_torrent_status);
|
||||
|
||||
// Full screen Icon
|
||||
TextView fullscreenText = activity.findViewById(R.id.exo_fullscreen);
|
||||
FrameLayout fullscreenButton = activity.findViewById(R.id.exo_fullscreen_button);
|
||||
|
||||
fullscreenText.setText(R.string.video_expand_icon);
|
||||
new Iconics.Builder().on(fullscreenText).build();
|
||||
|
||||
fullscreenButton.setOnClickListener(view -> {
|
||||
Log.d(TAG, "Fullscreen");
|
||||
fullScreenToggle();
|
||||
});
|
||||
|
||||
if (!mBound) {
|
||||
videoPlayerIntent = new Intent(context, VideoPlayerService.class);
|
||||
activity.bindService(videoPlayerIntent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void loadVideo() {
|
||||
Context context = getContext();
|
||||
|
||||
|
||||
// get video details from api
|
||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
|
||||
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class);
|
||||
|
||||
Call<Video> call = service.getVideoData(mVideoUuid);
|
||||
|
||||
call.enqueue(new Callback<Video>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Video> call, @NonNull Response<Video> response) {
|
||||
|
||||
Video video = response.body();
|
||||
|
||||
mService.setCurrentVideo(video);
|
||||
|
||||
if (video == null) {
|
||||
Toast.makeText(context, "Unable to retrieve video information, try again later.", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
playVideo(video);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) {
|
||||
Log.wtf(TAG, t.fillInStackTrace());
|
||||
ErrorHelper.showToastFromCommunicationError( getActivity(), t );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void useController(boolean value) {
|
||||
if (mBound) {
|
||||
simpleExoPlayerView.setUseController(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void playVideo(Video video) {
|
||||
|
||||
Context context = getContext();
|
||||
|
||||
// video Meta fragment
|
||||
VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment)
|
||||
requireActivity().getSupportFragmentManager().findFragmentById(R.id.video_meta_data_fragment);
|
||||
|
||||
assert videoMetaDataFragment != null;
|
||||
videoMetaDataFragment.updateVideoMeta(video, mService);
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
if (sharedPref.getBoolean(getString(R.string.pref_torrent_player_key), false)) {
|
||||
torrentStatus.setVisibility(View.VISIBLE);
|
||||
String stream = video.getFiles().get(0).getTorrentUrl();
|
||||
Log.v(TAG, "getTorrentUrl : " + video.getFiles().get(0).getTorrentUrl());
|
||||
torrentStream = setupTorrentStream();
|
||||
torrentStream.startStream(stream);
|
||||
} else {
|
||||
|
||||
Integer videoQuality = sharedPref.getInt(getString(R.string.pref_quality_key), 999999);
|
||||
|
||||
String urlToPlay = null;
|
||||
boolean isHLS = false;
|
||||
|
||||
// try HLS stream first
|
||||
// get video qualities
|
||||
// TODO: if auto is set all versions except 0p should be added to a track and have exoplayer auto select optimal bitrate
|
||||
if (video.getStreamingPlaylists().size() > 0) {
|
||||
urlToPlay = video.getStreamingPlaylists().get( 0 ).getPlaylistUrl();
|
||||
isHLS = true;
|
||||
} else {
|
||||
if (video.getFiles().size() > 0) {
|
||||
urlToPlay = video.getFiles().get( 0 ).getFileUrl(); // default, take first found, usually highest res
|
||||
for ( File file : video.getFiles() ) {
|
||||
// Set quality if it matches
|
||||
if ( file.getResolution().getId().equals( videoQuality ) ) {
|
||||
urlToPlay = file.getFileUrl();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!urlToPlay.isEmpty()) {
|
||||
mService.setCurrentStreamUrl( urlToPlay, isHLS);
|
||||
torrentStatus.setVisibility(View.GONE);
|
||||
startPlayer();
|
||||
} else {
|
||||
stopVideo();
|
||||
Toast.makeText(context, R.string.api_error, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
Log.v(TAG, "end of load Video");
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void startPlayer() {
|
||||
Util.startForegroundService(requireContext(), videoPlayerIntent);
|
||||
}
|
||||
|
||||
|
||||
public void destroyVideo() {
|
||||
simpleExoPlayerView.setPlayer(null);
|
||||
if (torrentStream != null) {
|
||||
torrentStream.stopStream();
|
||||
}
|
||||
}
|
||||
|
||||
public void pauseVideo() {
|
||||
if (mBound) {
|
||||
mService.player.setPlayWhenReady(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void pauseToggle() {
|
||||
if (mBound) {
|
||||
mService.player.setPlayWhenReady(!mService.player.getPlayWhenReady());
|
||||
}
|
||||
}
|
||||
|
||||
public void unPauseVideo() {
|
||||
if (mBound) {
|
||||
mService.player.setPlayWhenReady(true);
|
||||
}
|
||||
}
|
||||
|
||||
public float getVideoAspectRatio() { return aspectRatio; }
|
||||
|
||||
public boolean isPaused() {
|
||||
return !mService.player.getPlayWhenReady();
|
||||
}
|
||||
|
||||
public void showControls(boolean value) {
|
||||
simpleExoPlayerView.setUseController(value);
|
||||
}
|
||||
|
||||
public void stopVideo() {
|
||||
|
||||
if (mBound) {
|
||||
requireContext().unbindService(mConnection);
|
||||
mBound = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsFullscreen(Boolean fullscreen) {
|
||||
isFullscreen = fullscreen;
|
||||
|
||||
TextView fullscreenButton = requireActivity().findViewById(R.id.exo_fullscreen);
|
||||
if (fullscreen) {
|
||||
fullscreenButton.setText(R.string.video_compress_icon);
|
||||
} else {
|
||||
fullscreenButton.setText(R.string.video_expand_icon);
|
||||
}
|
||||
new Iconics.Builder().on(fullscreenButton).build();
|
||||
}
|
||||
|
||||
public Boolean getIsFullscreen() {
|
||||
return isFullscreen;
|
||||
}
|
||||
|
||||
public void fullScreenToggle() {
|
||||
if (!isFullscreen) {
|
||||
setIsFullscreen(true);
|
||||
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
} else {
|
||||
setIsFullscreen(false);
|
||||
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Torrent Playback
|
||||
*
|
||||
* @return torrent stream
|
||||
*/
|
||||
private TorrentStream setupTorrentStream() {
|
||||
|
||||
TorrentOptions torrentOptions = new TorrentOptions.Builder()
|
||||
.saveLocation(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
|
||||
.removeFilesAfterStop(true)
|
||||
.build();
|
||||
|
||||
TorrentStream torrentStream = TorrentStream.init(torrentOptions);
|
||||
|
||||
torrentStream.addListener(new TorrentListener() {
|
||||
@Override
|
||||
public void onStreamReady(Torrent torrent) {
|
||||
String videopath = Uri.fromFile(torrent.getVideoFile()).toString();
|
||||
Log.d(TAG, "Ready! torrentStream videopath:" + videopath);
|
||||
mService.setCurrentStreamUrl(videopath, false);
|
||||
startPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamProgress(Torrent torrent, StreamStatus streamStatus) {
|
||||
if (streamStatus.bufferProgress <= 100 && progressBar.getProgress() < 100 && progressBar.getProgress() != streamStatus.bufferProgress) {
|
||||
//Log.d(TAG, "Progress: " + streamStatus.bufferProgress);
|
||||
progressBar.setProgress(streamStatus.bufferProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamStopped() {
|
||||
Log.d(TAG, "Stopped");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamPrepared(Torrent torrent) {
|
||||
Log.d(TAG, "Prepared");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamStarted(Torrent torrent) {
|
||||
Log.d(TAG, "Started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamError(Torrent torrent, Exception e) {
|
||||
Log.d(TAG, "Error: " + e.getMessage());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return torrentStream;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onVideoEnabled(DecoderCounters counters) {
|
||||
Log.v(TAG, "onVideoEnabled()...");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoInputFormatChanged(Format format) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDroppedFrames(int count, long elapsedMs) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame(Surface surface) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDisabled(DecoderCounters counters) {
|
||||
Log.v(TAG, "onVideoDisabled()...");
|
||||
}
|
||||
|
||||
View.OnTouchListener touchListener = new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
return mDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public String getVideoUuid() {
|
||||
return mVideoUuid;
|
||||
}
|
||||
|
||||
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
/*
|
||||
@Override
|
||||
public boolean onDown(MotionEvent event) {
|
||||
Log.d("TAG","onDown: ");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
Log.i("TAG", "onSingleTapConfirmed: ");
|
||||
pauseToggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
Log.i("TAG", "onLongPress: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
Log.i("TAG", "onDoubleTap: ");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2,
|
||||
float distanceX, float distanceY) {
|
||||
Log.i("TAG", "onScroll: ");
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
||||
float velocityX, float velocityY) {
|
||||
Log.d(TAG, event1.toString());
|
||||
Log.d(TAG, event2.toString());
|
||||
Log.d(TAG, String.valueOf(velocityX));
|
||||
Log.d(TAG, String.valueOf(velocityY));
|
||||
//arbitrarily velocity speeds that seem to work to differentiate events.
|
||||
if (velocityY > 4000) {
|
||||
Log.d(TAG, "we have a drag down event");
|
||||
if (canEnterPipMode(getContext())) {
|
||||
requireActivity().enterPictureInPictureMode();
|
||||
}
|
||||
}
|
||||
if ((velocityX > 2000) && (Math.abs(velocityY) < 2000)) {
|
||||
Log.d(TAG, "swipe right " + velocityY);
|
||||
}
|
||||
if ((velocityX < 2000) && (Math.abs(velocityY) < 2000)) {
|
||||
Log.d(TAG, "swipe left " + velocityY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
* 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 com.google.android.exoplayer2.video.VideoRendererEventListener
|
||||
import com.google.android.exoplayer2.ui.PlayerView
|
||||
import android.content.Intent
|
||||
import net.schueller.peertube.service.VideoPlayerService
|
||||
import com.github.se_bastiaan.torrentstream.TorrentStream
|
||||
import android.widget.LinearLayout
|
||||
import android.view.GestureDetector
|
||||
import android.content.ServiceConnection
|
||||
import android.content.ComponentName
|
||||
import android.os.IBinder
|
||||
import net.schueller.peertube.service.VideoPlayerService.LocalBinder
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.AspectRatioListener
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.os.Bundle
|
||||
import net.schueller.peertube.R
|
||||
import android.app.Activity
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.Context
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.FrameLayout
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import net.schueller.peertube.network.GetVideoDataService
|
||||
import net.schueller.peertube.network.RetrofitInstance
|
||||
import android.widget.Toast
|
||||
import net.schueller.peertube.helper.ErrorHelper
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener
|
||||
import com.github.se_bastiaan.torrentstream.Torrent
|
||||
import com.github.se_bastiaan.torrentstream.StreamStatus
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters
|
||||
import android.view.View.OnTouchListener
|
||||
import android.view.MotionEvent
|
||||
import android.view.GestureDetector.SimpleOnGestureListener
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ProgressBar
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.github.se_bastiaan.torrentstream.TorrentOptions.Builder
|
||||
import com.google.android.exoplayer2.Format
|
||||
import com.google.android.exoplayer2.util.Util
|
||||
import com.mikepenz.iconics.Iconics
|
||||
import net.schueller.peertube.R.layout
|
||||
import net.schueller.peertube.R.string
|
||||
import net.schueller.peertube.helper.VideoHelper
|
||||
import net.schueller.peertube.model.Video
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.lang.Exception
|
||||
import kotlin.math.abs
|
||||
|
||||
class VideoPlayerFragment : Fragment(), VideoRendererEventListener {
|
||||
|
||||
var videoUuid: String? = null
|
||||
private set
|
||||
// private var progressBar: ProgressBar? = null
|
||||
private var exoPlayer: PlayerView? = null
|
||||
private var videoPlayerIntent: Intent? = null
|
||||
private var mBound = false
|
||||
private var isFullscreen = false
|
||||
private var mService: VideoPlayerService? = null
|
||||
private var torrentStream: TorrentStream? = null
|
||||
// private var torrentStatus: LinearLayout? = null
|
||||
var videoAspectRatio = 0f
|
||||
private set
|
||||
private var mDetector: GestureDetector? = null
|
||||
private val mConnection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
Log.d(TAG, "onServiceConnected")
|
||||
val binder = service as LocalBinder
|
||||
mService = binder.service
|
||||
|
||||
// 2. Create the player
|
||||
exoPlayer!!.player = mService!!.player
|
||||
mBound = true
|
||||
loadVideo()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(componentName: ComponentName) {
|
||||
Log.d(TAG, "onServiceDisconnected")
|
||||
exoPlayer!!.player = null
|
||||
mBound = false
|
||||
}
|
||||
}
|
||||
private val aspectRatioListener: AspectRatioListener = AspectRatioListener {
|
||||
targetAspectRatio, _, _ -> videoAspectRatio = targetAspectRatio
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(layout.fragment_video_player, container, false)
|
||||
}
|
||||
|
||||
fun start(videoUuid: String?) {
|
||||
|
||||
// start service
|
||||
val context = context
|
||||
val activity: Activity? = activity
|
||||
this.videoUuid = videoUuid
|
||||
assert(activity != null)
|
||||
// progressBar = activity?.findViewById(R.id.torrent_progress)
|
||||
// progressBar?.max = 100
|
||||
assert(context != null)
|
||||
exoPlayer = PlayerView(context!!)
|
||||
exoPlayer = activity?.findViewById(R.id.video_view)
|
||||
exoPlayer?.controllerShowTimeoutMs = 1000
|
||||
exoPlayer?.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||
mDetector = GestureDetector(context, MyGestureListener())
|
||||
exoPlayer?.setOnTouchListener(touchListener)
|
||||
exoPlayer?.setAspectRatioListener(aspectRatioListener)
|
||||
// torrentStatus = activity?.findViewById(R.id.exo_torrent_status)
|
||||
|
||||
// Full screen Icon
|
||||
val fullscreenText = activity?.findViewById<TextView>(R.id.exo_fullscreen)
|
||||
val fullscreenButton = activity?.findViewById<FrameLayout>(R.id.exo_fullscreen_button)
|
||||
fullscreenText?.setText(string.video_expand_icon)
|
||||
if (fullscreenText != null) {
|
||||
Iconics.Builder().on(fullscreenText).build()
|
||||
fullscreenButton?.setOnClickListener {
|
||||
Log.d(TAG, "Fullscreen")
|
||||
fullScreenToggle()
|
||||
}
|
||||
}
|
||||
if (!mBound) {
|
||||
videoPlayerIntent = Intent(context, VideoPlayerService::class.java)
|
||||
activity?.bindService(videoPlayerIntent, mConnection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVideo() {
|
||||
val context = context
|
||||
|
||||
// get video details from api
|
||||
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||
val service =
|
||||
RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(
|
||||
GetVideoDataService::class.java
|
||||
)
|
||||
val call = service.getVideoData(videoUuid)
|
||||
call.enqueue(object : Callback<Video?> {
|
||||
override fun onResponse(call: Call<Video?>, response: Response<Video?>) {
|
||||
val video = response.body()
|
||||
mService!!.setCurrentVideo(video)
|
||||
if (video == null) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Unable to retrieve video information, try again later.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
playVideo(video)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Video?>, t: Throwable) {
|
||||
Log.wtf(TAG, t.fillInStackTrace())
|
||||
ErrorHelper.showToastFromCommunicationError(activity, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun useController(value: Boolean) {
|
||||
if (mBound) {
|
||||
exoPlayer!!.useController = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun playVideo(video: Video) {
|
||||
val context = context
|
||||
|
||||
// video Meta fragment
|
||||
val videoMetaDataFragment =
|
||||
(requireActivity().supportFragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment?)!!
|
||||
videoMetaDataFragment.updateVideoMeta(video, mService)
|
||||
|
||||
val sharedPref = context?.getSharedPreferences(
|
||||
context.packageName + "_preferences",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
var prefTorrentPlayer = false
|
||||
var videoQuality = 999999
|
||||
if (sharedPref != null) {
|
||||
prefTorrentPlayer = sharedPref.getBoolean(getString(string.pref_torrent_player_key), false)
|
||||
videoQuality = sharedPref.getInt(getString(string.pref_quality_key), 999999)
|
||||
}
|
||||
|
||||
// if (prefTorrentPlayer) {
|
||||
// torrentStatus!!.visibility = View.VISIBLE
|
||||
// val stream = video.files[0].torrentUrl
|
||||
// Log.v(TAG, "getTorrentUrl : " + video.files[0].torrentUrl)
|
||||
// torrentStream = setupTorrentStream()
|
||||
// torrentStream!!.startStream(stream)
|
||||
// } else {
|
||||
var urlToPlay: String? = null
|
||||
var isHLS = false
|
||||
|
||||
// try HLS stream first
|
||||
// get video qualities
|
||||
// TODO: if auto is set all versions except 0p should be added to a track and have exoplayer auto select optimal bitrate
|
||||
if (video.streamingPlaylists.size > 0) {
|
||||
urlToPlay = video.streamingPlaylists[0].playlistUrl
|
||||
isHLS = true
|
||||
} else {
|
||||
if (video.files.size > 0) {
|
||||
urlToPlay = video.files[0].fileUrl // default, take first found, usually highest res
|
||||
for (file in video.files) {
|
||||
// Set quality if it matches
|
||||
if (file.resolution.id == videoQuality) {
|
||||
urlToPlay = file.fileUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (urlToPlay!!.isNotEmpty()) {
|
||||
mService!!.setCurrentStreamUrl(urlToPlay, isHLS)
|
||||
// torrentStatus!!.visibility = View.GONE
|
||||
startPlayer()
|
||||
} else {
|
||||
stopVideo()
|
||||
Toast.makeText(context, string.api_error, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
// }
|
||||
Log.v(TAG, "end of load Video")
|
||||
}
|
||||
|
||||
private fun startPlayer() {
|
||||
Util.startForegroundService(requireContext(), videoPlayerIntent!!)
|
||||
}
|
||||
|
||||
fun destroyVideo() {
|
||||
exoPlayer!!.player = null
|
||||
if (torrentStream != null) {
|
||||
torrentStream!!.stopStream()
|
||||
}
|
||||
}
|
||||
|
||||
fun pauseVideo() {
|
||||
if (mBound) {
|
||||
mService!!.player!!.playWhenReady = false
|
||||
}
|
||||
}
|
||||
|
||||
// fun pauseToggle() {
|
||||
// if (mBound) {
|
||||
// mService!!.player!!.playWhenReady = !mService!!.player!!.playWhenReady
|
||||
// }
|
||||
// }
|
||||
|
||||
fun unPauseVideo() {
|
||||
if (mBound) {
|
||||
mService!!.player!!.playWhenReady = true
|
||||
}
|
||||
}
|
||||
|
||||
val isPaused: Boolean
|
||||
get() = !mService!!.player!!.playWhenReady
|
||||
|
||||
fun showControls(value: Boolean) {
|
||||
exoPlayer!!.useController = value
|
||||
}
|
||||
|
||||
fun stopVideo() {
|
||||
if (mBound) {
|
||||
requireContext().unbindService(mConnection)
|
||||
mBound = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* triggered rotation and button press
|
||||
*/
|
||||
fun setIsFullscreen(fullscreen: Boolean) {
|
||||
isFullscreen = fullscreen
|
||||
val fullscreenButton = requireActivity().findViewById<TextView>(R.id.exo_fullscreen)
|
||||
if (fullscreen) {
|
||||
hideSystemBars()
|
||||
fullscreenButton.setText(string.video_compress_icon)
|
||||
} else {
|
||||
restoreSystemBars()
|
||||
fullscreenButton.setText(string.video_expand_icon)
|
||||
}
|
||||
Iconics.Builder().on(fullscreenButton).build()
|
||||
}
|
||||
|
||||
private fun hideSystemBars()
|
||||
{
|
||||
val view = this.view
|
||||
if (view != null) {
|
||||
val windowInsetsController =
|
||||
ViewCompat.getWindowInsetsController(view) ?: return
|
||||
// Configure the behavior of the hidden system bars
|
||||
windowInsetsController.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
// Hide both the status bar and the navigation bar
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreSystemBars()
|
||||
{
|
||||
val view = this.view
|
||||
if (view != null) {
|
||||
val windowInsetsController =
|
||||
ViewCompat.getWindowInsetsController(view) ?: return
|
||||
// Show both the status bar and the navigation bar
|
||||
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
}
|
||||
|
||||
fun getIsFullscreen(): Boolean {
|
||||
return isFullscreen
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered by button press
|
||||
*/
|
||||
fun fullScreenToggle() {
|
||||
if (!isFullscreen) {
|
||||
setIsFullscreen(true)
|
||||
requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
} else {
|
||||
setIsFullscreen(false)
|
||||
// we want to force portrait if fullscreen is switched of as we do not have a min. landscape view
|
||||
requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
}
|
||||
//
|
||||
// /**
|
||||
// * Torrent Playback
|
||||
// *
|
||||
// * @return torrent stream
|
||||
// */
|
||||
// private fun setupTorrentStream(): TorrentStream {
|
||||
// val torrentOptions = Builder()
|
||||
// .saveLocation(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
|
||||
// .removeFilesAfterStop(true)
|
||||
// .build()
|
||||
// val torrentStream = TorrentStream.init(torrentOptions)
|
||||
// torrentStream.addListener(object : TorrentListener {
|
||||
// override fun onStreamReady(torrent: Torrent) {
|
||||
// val videoPath = Uri.fromFile(torrent.videoFile).toString()
|
||||
// Log.d(TAG, "Ready! torrentStream videoPath:$videoPath")
|
||||
// mService!!.setCurrentStreamUrl(videoPath, false)
|
||||
// startPlayer()
|
||||
// }
|
||||
//
|
||||
// override fun onStreamProgress(torrent: Torrent, streamStatus: StreamStatus) {
|
||||
// if (streamStatus.bufferProgress <= 100 && progressBar!!.progress < 100 && progressBar!!.progress != streamStatus.bufferProgress) {
|
||||
// //Log.d(TAG, "Progress: " + streamStatus.bufferProgress);
|
||||
// progressBar!!.progress = streamStatus.bufferProgress
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onStreamStopped() {
|
||||
// Log.d(TAG, "Stopped")
|
||||
// }
|
||||
//
|
||||
// override fun onStreamPrepared(torrent: Torrent) {
|
||||
// Log.d(TAG, "Prepared")
|
||||
// }
|
||||
//
|
||||
// override fun onStreamStarted(torrent: Torrent) {
|
||||
// Log.d(TAG, "Started")
|
||||
// }
|
||||
//
|
||||
// override fun onStreamError(torrent: Torrent, e: Exception) {
|
||||
// Log.d(TAG, "Error: " + e.message)
|
||||
// }
|
||||
// })
|
||||
// return torrentStream
|
||||
// }
|
||||
|
||||
override fun onVideoEnabled(counters: DecoderCounters) {
|
||||
Log.v(TAG, "onVideoEnabled()...")
|
||||
}
|
||||
|
||||
override fun onVideoDecoderInitialized(
|
||||
decoderName: String,
|
||||
initializedTimestampMs: Long,
|
||||
initializationDurationMs: Long
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onVideoInputFormatChanged(format: Format) {}
|
||||
override fun onDroppedFrames(count: Int, elapsedMs: Long) {}
|
||||
override fun onVideoDisabled(counters: DecoderCounters) {
|
||||
Log.v(TAG, "onVideoDisabled()...")
|
||||
}
|
||||
|
||||
// touch event on video player
|
||||
private var touchListener = OnTouchListener { _, event ->
|
||||
//v.performClick() // causes flicker but should be implemented for accessibility
|
||||
mDetector!!.onTouchEvent(event)
|
||||
}
|
||||
|
||||
internal inner class MyGestureListener : SimpleOnGestureListener() {
|
||||
|
||||
/*
|
||||
@Override
|
||||
public boolean onDown(MotionEvent event) {
|
||||
Log.d("TAG","onDown: ");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
Log.i("TAG", "onSingleTapConfirmed: ");
|
||||
pauseToggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
Log.i("TAG", "onLongPress: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
Log.i("TAG", "onDoubleTap: ");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2,
|
||||
float distanceX, float distanceY) {
|
||||
Log.i("TAG", "onScroll: ");
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
@RequiresApi(api = VERSION_CODES.N)
|
||||
override fun onFling(
|
||||
event1: MotionEvent, event2: MotionEvent,
|
||||
velocityX: Float, velocityY: Float
|
||||
): Boolean {
|
||||
Log.d(TAG, event1.toString())
|
||||
Log.d(TAG, event2.toString())
|
||||
Log.d(TAG, velocityX.toString())
|
||||
Log.d(TAG, velocityY.toString())
|
||||
//arbitrarily velocity speeds that seem to work to differentiate events.
|
||||
if (velocityY > 4000) {
|
||||
Log.d(TAG, "we have a drag down event")
|
||||
if (VideoHelper.canEnterPipMode(context)) {
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.O) {
|
||||
val pipParams = PictureInPictureParams.Builder()
|
||||
requireActivity().enterPictureInPictureMode(pipParams.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
if (velocityX > 2000 && abs(velocityY) < 2000) {
|
||||
Log.d(TAG, "swipe right $velocityY")
|
||||
}
|
||||
if (velocityX < 2000 && abs(velocityY) < 2000) {
|
||||
Log.d(TAG, "swipe left $velocityY")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "VideoPlayerFragment"
|
||||
}
|
||||
}
|
|
@ -18,32 +18,92 @@ package net.schueller.peertube.helper
|
|||
|
||||
import android.content.Context
|
||||
import android.text.format.DateUtils
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.R.string
|
||||
import net.schueller.peertube.model.Account
|
||||
import net.schueller.peertube.model.Avatar
|
||||
import net.schueller.peertube.model.Video
|
||||
import org.ocpsoft.prettytime.PrettyTime
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
object MetaDataHelper {
|
||||
|
||||
@JvmStatic
|
||||
fun getMetaString(getCreatedAt: Date, viewCount: Int, context: Context): String {
|
||||
fun getMetaString(getCreatedAt: Date, viewCount: Int, context: Context, reversed: Boolean = false): String {
|
||||
|
||||
// Compatible with SDK 21+
|
||||
val currentLanguage = Locale.getDefault().displayLanguage
|
||||
val p = PrettyTime(currentLanguage)
|
||||
val relativeTime = p.format(Date(getCreatedAt.time))
|
||||
return relativeTime +
|
||||
context.resources.getString(string.meta_data_seperator) +
|
||||
viewCount + context.resources.getString(string.meta_data_views)
|
||||
return if (reversed) {
|
||||
viewCount.toString() +
|
||||
context.resources.getString(string.meta_data_views) +
|
||||
context.resources.getString(string.meta_data_seperator) +
|
||||
relativeTime
|
||||
} else {
|
||||
relativeTime +
|
||||
context.resources.getString(string.meta_data_seperator) +
|
||||
viewCount + context.resources.getString(string.meta_data_views)
|
||||
}
|
||||
}
|
||||
|
||||
fun getTagsString(video: Video): String {
|
||||
return if (video.tags.isNotEmpty()) {
|
||||
" #" + video.tags.joinToString(" #", "", "", 3, "")
|
||||
} else {
|
||||
" "
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOwnerString(accountName: String, serverHost: String, context: Context): String {
|
||||
return accountName +
|
||||
context.resources.getString(string.meta_data_owner_seperator) +
|
||||
serverHost
|
||||
fun getCreatorString(video: Video, context: Context, fqdn: Boolean = false): String {
|
||||
return if (isChannel(video)) {
|
||||
if (!fqdn) {
|
||||
video.channel.displayName
|
||||
} else {
|
||||
getConcatFqdnString(video.channel.name, video.channel.host, context)
|
||||
}
|
||||
} else {
|
||||
getOwnerString(video.account, context, fqdn)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOwnerString(account: Account, context: Context, fqdn: Boolean = true): String {
|
||||
return if (!fqdn) {
|
||||
account.name
|
||||
} else {
|
||||
getConcatFqdnString(account.name, account.host, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConcatFqdnString(user: String, host: String, context: Context): String {
|
||||
return context.resources.getString(string.video_owner_fqdn_line, user, host)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getCreatorAvatar(video: Video, context: Context): Avatar? {
|
||||
return if (isChannel(video)) {
|
||||
if (video.channel.avatar == null) {
|
||||
video.account.avatar
|
||||
} else {
|
||||
video.channel.avatar
|
||||
}
|
||||
} else {
|
||||
video.account.avatar
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isChannel(video: Video): Boolean {
|
||||
// c285b523-d688-43c5-a9ad-f745ff09bbd1
|
||||
return !video.channel.name.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}".toRegex())
|
||||
}
|
||||
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun getDuration(duration: Long?): String {
|
||||
return DateUtils.formatElapsedTime(duration!!)
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* 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.intents;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.webkit.URLUtil;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
import com.github.se_bastiaan.torrentstream.TorrentOptions;
|
||||
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.model.Video;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
|
||||
public class Intents {
|
||||
|
||||
private static final String TAG = "Intents";
|
||||
|
||||
/**
|
||||
* https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c
|
||||
*
|
||||
* @param context context
|
||||
* @param video video
|
||||
*/
|
||||
// TODO, offer which version to download
|
||||
public static void Share(Context context, Video video) {
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, video.getName());
|
||||
intent.putExtra(Intent.EXTRA_TEXT, APIUrlHelper.getShareUrl(context, video.getUuid()) );
|
||||
intent.setType("text/plain");
|
||||
|
||||
context.startActivity(intent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param context context
|
||||
* @param video video
|
||||
*/
|
||||
// TODO, offer which version to download
|
||||
public static void Download(Context context, Video video) {
|
||||
|
||||
if (video.getFiles().size() > 0)
|
||||
{
|
||||
String url = video.getFiles().get( 0 ).getFileDownloadUrl();
|
||||
// make sure it is a valid filename
|
||||
String destFilename = video.getName().replaceAll( "[^a-zA-Z0-9]", "_" ) + "." + MimeTypeMap.getFileExtensionFromUrl( URLUtil.guessFileName( url, null, null ) );
|
||||
|
||||
//Toast.makeText(context, destFilename, Toast.LENGTH_LONG).show();
|
||||
DownloadManager.Request request = new DownloadManager.Request( Uri.parse( url ) );
|
||||
request.setDescription( video.getDescription() );
|
||||
request.setTitle( video.getName() );
|
||||
request.allowScanningByMediaScanner();
|
||||
request.setNotificationVisibility( DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED );
|
||||
request.setDestinationInExternalPublicDir( Environment.DIRECTORY_DOWNLOADS, destFilename );
|
||||
|
||||
// get download service and enqueue file
|
||||
DownloadManager manager = (DownloadManager) context.getSystemService( Context.DOWNLOAD_SERVICE );
|
||||
manager.enqueue( request );
|
||||
} else {
|
||||
Toast.makeText( context, R.string.api_error, Toast.LENGTH_LONG ).show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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.intents
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.os.Environment
|
||||
import android.webkit.URLUtil
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.model.Video
|
||||
import android.content.ContextWrapper
|
||||
|
||||
import android.app.Activity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
object Intents {
|
||||
private const val TAG = "Intents"
|
||||
|
||||
/**
|
||||
* https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c
|
||||
*
|
||||
* @param context context
|
||||
* @param video video
|
||||
*/
|
||||
// TODO, offer which version to download
|
||||
@JvmStatic
|
||||
fun Share(context: Context, video: Video) {
|
||||
val intent = Intent()
|
||||
intent.action = Intent.ACTION_SEND
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, video.name)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, APIUrlHelper.getShareUrl(context, video.uuid))
|
||||
intent.type = "text/plain"
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context context
|
||||
* @param video video
|
||||
*/
|
||||
// TODO, offer which version to download
|
||||
fun Download(context: Context, video: Video) {
|
||||
|
||||
// deal withe permissions here
|
||||
|
||||
// get permission to store file
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
val activity = getActivity(context)
|
||||
|
||||
if (activity != null) {
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
startDownload(video, context)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.video_download_permission_error),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
startDownload(video, context)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun startDownload(video: Video, context: Context)
|
||||
{
|
||||
if (video.files.size > 0) {
|
||||
val url = video.files[0].fileDownloadUrl
|
||||
// make sure it is a valid filename
|
||||
val destFilename = video.name.replace(
|
||||
"[^a-zA-Z0-9]".toRegex(),
|
||||
"_"
|
||||
) + "." + MimeTypeMap.getFileExtensionFromUrl(
|
||||
URLUtil.guessFileName(url, null, null)
|
||||
)
|
||||
|
||||
//Toast.makeText(context, destFilename, Toast.LENGTH_LONG).show();
|
||||
val request = DownloadManager.Request(Uri.parse(url))
|
||||
request.setDescription(video.description)
|
||||
request.setTitle(video.name)
|
||||
request.allowScanningByMediaScanner()
|
||||
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, destFilename)
|
||||
|
||||
// get download service and enqueue file
|
||||
val manager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
manager.enqueue(request)
|
||||
} else {
|
||||
Toast.makeText(context, R.string.api_error, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getActivity(context: Context?): Activity? {
|
||||
if (context == null) {
|
||||
return null
|
||||
} else if (context is ContextWrapper) {
|
||||
return if (context is Activity) {
|
||||
context
|
||||
} else {
|
||||
getActivity(context.baseContext)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.model
|
||||
|
||||
import java.util.*
|
||||
|
||||
|
||||
class Comment(
|
||||
|
||||
val id: Int,
|
||||
val url: String,
|
||||
val text: String,
|
||||
val threadId: Int,
|
||||
val inReplyToCommentId: Int? = null,
|
||||
val videoId: Int,
|
||||
val createdAt: Date,
|
||||
val updatedAt: Date,
|
||||
val deletedAt: Date? = null,
|
||||
val isDeleted: Boolean,
|
||||
val totalRepliesFromVideoAuthor: Int,
|
||||
val totalReplies: Int,
|
||||
val account: Account
|
||||
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import net.schueller.peertube.model.ui.OverviewRecycleViewItem
|
||||
import java.util.ArrayList
|
||||
|
||||
class CommentThread(
|
||||
val total: Int,
|
||||
val totalNotDeletedComments: Int,
|
||||
@SerializedName("data")
|
||||
val comments: ArrayList<Comment>
|
||||
|
||||
): OverviewRecycleViewItem()
|
|
@ -35,7 +35,7 @@ class Video(
|
|||
var licence: Licence,
|
||||
var language: Language,
|
||||
var nsfw: Boolean,
|
||||
var description: String,
|
||||
var description: String? = null,
|
||||
var local: Boolean,
|
||||
var live: Boolean,
|
||||
var duration: Int,
|
||||
|
@ -66,7 +66,7 @@ class Video(
|
|||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun getMediaDescription(context: Context?, video: Video): MediaDescriptionCompat {
|
||||
fun getMediaDescription(video: Video): MediaDescriptionCompat {
|
||||
|
||||
// String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package net.schueller.peertube.model.ui
|
||||
|
||||
import net.schueller.peertube.model.Video
|
||||
|
||||
class VideoMetaViewItem: OverviewRecycleViewItem() {
|
||||
var video: Video? = null
|
||||
}
|
|
@ -16,15 +16,23 @@
|
|||
*/
|
||||
package net.schueller.peertube.network;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import net.schueller.peertube.model.Account;
|
||||
import net.schueller.peertube.model.Channel;
|
||||
import net.schueller.peertube.model.ChannelList;
|
||||
import net.schueller.peertube.model.Me;
|
||||
import net.schueller.peertube.model.VideoList;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
|
@ -52,5 +60,18 @@ public interface GetUserService {
|
|||
@Path(value = "displayName", encoded = true) String displayName
|
||||
);
|
||||
|
||||
@GET("users/me/subscriptions/exist")
|
||||
Call<JsonObject> subscriptionsExist(
|
||||
@Query("uris") String videoChannelNameAndHost
|
||||
);
|
||||
|
||||
@POST("users/me/subscriptions")
|
||||
Call<ResponseBody> subscribe(
|
||||
@Body RequestBody params
|
||||
);
|
||||
|
||||
@DELETE("users/me/subscriptions/{videoChannelNameAndHost}")
|
||||
Call<ResponseBody> unsubscribe(
|
||||
@Path(value = "videoChannelNameAndHost", encoded = true) String videoChannelNameAndHost
|
||||
);
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package net.schueller.peertube.network;
|
||||
|
||||
import net.schueller.peertube.model.CommentThread;
|
||||
import net.schueller.peertube.model.Description;
|
||||
import net.schueller.peertube.model.Overview;
|
||||
import net.schueller.peertube.model.Rating;
|
||||
|
@ -91,4 +92,12 @@ public interface GetVideoDataService {
|
|||
@Query("page") int page
|
||||
);
|
||||
|
||||
// https://troll.tv/api/v1/videos/{id}/comment-threads?start=0&count=10&sort=-createdAt
|
||||
@GET("videos/{id}/comment-threads")
|
||||
Call<CommentThread> getCommentThreads(
|
||||
@Path(value = "id", encoded = true) Integer id,
|
||||
@Query("start") int start,
|
||||
@Query("count") int count,
|
||||
@Query("sort") String sort
|
||||
);
|
||||
}
|
|
@ -1,358 +0,0 @@
|
|||
/*
|
||||
* 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.service;
|
||||
|
||||
import static android.media.session.PlaybackState.ACTION_PAUSE;
|
||||
import static android.media.session.PlaybackState.ACTION_PLAY;
|
||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
|
||||
import static net.schueller.peertube.activity.VideoListActivity.EXTRA_VIDEOID;
|
||||
import static net.schueller.peertube.network.UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.util.Log;
|
||||
import android.webkit.URLUtil;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
||||
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;
|
||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.activity.VideoPlayActivity;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.helper.MetaDataHelper;
|
||||
import net.schueller.peertube.model.Video;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class VideoPlayerService extends Service {
|
||||
|
||||
private static final String TAG = "VideoPlayerService";
|
||||
|
||||
private static final String MEDIA_SESSION_TAG = "peertube_player";
|
||||
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
|
||||
private static final String PLAYBACK_CHANNEL_ID = "playback_channel";
|
||||
|
||||
private static final Integer PLAYBACK_NOTIFICATION_ID = 1;
|
||||
|
||||
public SimpleExoPlayer player;
|
||||
|
||||
private Video currentVideo;
|
||||
|
||||
private String currentStreamUrl;
|
||||
|
||||
private boolean currentStreamUrlIsHLS;
|
||||
|
||||
private PlayerNotificationManager playerNotificationManager;
|
||||
|
||||
private IntentFilter becomeNoisyIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
||||
|
||||
private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.v(TAG, "onCreate...");
|
||||
|
||||
super.onCreate();
|
||||
|
||||
player = new SimpleExoPlayer.Builder(getApplicationContext())
|
||||
.setTrackSelector(new DefaultTrackSelector(getApplicationContext()))
|
||||
.build();
|
||||
|
||||
// Stop player if audio device changes, e.g. headphones unplugged
|
||||
player.addListener(new Player.EventListener() {
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
|
||||
if (playbackState == ACTION_PAUSE) { // this means that pause is available, hence the audio is playing
|
||||
Log.v(TAG, "ACTION_PLAY: " + playbackState);
|
||||
registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter);
|
||||
}
|
||||
|
||||
if (playbackState
|
||||
== ACTION_PLAY) { // this means that play is available, hence the audio is paused or stopped
|
||||
Log.v(TAG, "ACTION_PAUSE: " + playbackState);
|
||||
safeUnregisterReceiver();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
|
||||
public VideoPlayerService getService() {
|
||||
// Return this instance of VideoPlayerService so clients can call public methods
|
||||
return VideoPlayerService.this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
Log.v(TAG, "onDestroy...");
|
||||
if (playerNotificationManager != null) {
|
||||
playerNotificationManager.setPlayer(null);
|
||||
}
|
||||
//Was seeing an error when exiting the program about not unregistering the receiver.
|
||||
safeUnregisterReceiver();
|
||||
|
||||
if (player != null) {
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void safeUnregisterReceiver()
|
||||
{
|
||||
try {
|
||||
unregisterReceiver(myNoisyAudioStreamReceiver);
|
||||
} catch (Exception e) {
|
||||
Log.e("VideoPlayerService", "attempted to unregister a nonregistered service");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Context context = this;
|
||||
Log.v(TAG, "onStartCommand...");
|
||||
|
||||
if (!URLUtil.isValidUrl(currentStreamUrl)) {
|
||||
Toast.makeText(context, "Invalid URL provided. Unable to play video.", Toast.LENGTH_SHORT).show();
|
||||
return START_NOT_STICKY;
|
||||
} else {
|
||||
playVideo();
|
||||
return START_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setCurrentVideo(Video video) {
|
||||
Log.v(TAG, "setCurrentVideo...");
|
||||
currentVideo = video;
|
||||
}
|
||||
|
||||
public void setCurrentStreamUrl(String streamUrl, boolean isHLS) {
|
||||
Log.v(TAG, "setCurrentStreamUrl..." + streamUrl);
|
||||
currentStreamUrlIsHLS = isHLS;
|
||||
currentStreamUrl = streamUrl;
|
||||
}
|
||||
|
||||
//Playback speed control
|
||||
public void setPlayBackSpeed(float speed) {
|
||||
Log.v(TAG, "setPlayBackSpeed...");
|
||||
player.setPlaybackParameters(new PlaybackParameters(speed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current playback speed of the player.
|
||||
*
|
||||
* @return the current playback speed of the player.
|
||||
*/
|
||||
public float getPlayBackSpeed() {
|
||||
return player.getPlaybackParameters().speed;
|
||||
}
|
||||
|
||||
public void playVideo() {
|
||||
Context context = this;
|
||||
|
||||
// We need a valid URL
|
||||
|
||||
Log.v(TAG, "playVideo...");
|
||||
|
||||
// Produces DataSource instances through which media data is loaded.
|
||||
// DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
|
||||
// Util.getUserAgent(getApplicationContext(), "PeerTube"), null);
|
||||
|
||||
OkHttpClient.Builder okhttpClientBuilder;
|
||||
|
||||
if (!APIUrlHelper.useInsecureConnection(this)) {
|
||||
okhttpClientBuilder = new OkHttpClient.Builder();
|
||||
} else {
|
||||
okhttpClientBuilder = getUnsafeOkHttpClientBuilder();
|
||||
}
|
||||
|
||||
// Create a data source factory.
|
||||
DataSource.Factory dataSourceFactory = new OkHttpDataSourceFactory(okhttpClientBuilder.build(), Util.getUserAgent(getApplicationContext(), "PeerTube"));
|
||||
|
||||
// Create a progressive media source pointing to a stream uri.
|
||||
MediaSource mediaSource;
|
||||
if (currentStreamUrlIsHLS) {
|
||||
mediaSource = new HlsMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)));
|
||||
} else {
|
||||
mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)));
|
||||
}
|
||||
|
||||
// Set the media source to be played.
|
||||
player.setMediaSource(mediaSource);
|
||||
|
||||
// Prepare the player.
|
||||
player.prepare();
|
||||
|
||||
// Auto play
|
||||
player.setPlayWhenReady(true);
|
||||
|
||||
//set playback speed to global default
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
float speed = Float.parseFloat(sharedPref.getString(getString(R.string.pref_video_speed_key), "1.0"));
|
||||
|
||||
this.setPlayBackSpeed(speed);
|
||||
|
||||
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
|
||||
context, PLAYBACK_CHANNEL_ID, R.string.playback_channel_name,
|
||||
PLAYBACK_NOTIFICATION_ID,
|
||||
new PlayerNotificationManager.MediaDescriptionAdapter() {
|
||||
|
||||
@Override
|
||||
public String getCurrentContentTitle(Player player) {
|
||||
return currentVideo.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PendingIntent createCurrentContentIntent(Player player) {
|
||||
Intent intent = new Intent(context, VideoPlayActivity.class);
|
||||
intent.putExtra(EXTRA_VIDEOID, currentVideo.getUuid());
|
||||
return PendingIntent.getActivity(context, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentContentText(Player player) {
|
||||
return MetaDataHelper.getMetaString(
|
||||
currentVideo.getCreatedAt(),
|
||||
currentVideo.getViews(),
|
||||
getBaseContext()
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bitmap getCurrentLargeIcon(Player player,
|
||||
PlayerNotificationManager.BitmapCallback callback) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
playerNotificationManager.setSmallIcon(R.drawable.ic_logo_bw);
|
||||
|
||||
// don't show skip buttons in notification
|
||||
playerNotificationManager.setUseNavigationActions(false);
|
||||
playerNotificationManager.setUseStopAction(true);
|
||||
|
||||
playerNotificationManager.setNotificationListener(
|
||||
new PlayerNotificationManager.NotificationListener() {
|
||||
@Override
|
||||
public void onNotificationStarted(int notificationId, Notification notification) {
|
||||
startForeground(notificationId, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationCancelled(int notificationId) {
|
||||
Log.v(TAG, "onNotificationCancelled...");
|
||||
stopForeground(true);
|
||||
Intent killFloat = new Intent(ACTION_STOP);
|
||||
sendBroadcast(killFloat);
|
||||
/*
|
||||
Intent killFloat = new Intent(BROADCAST_ACTION);
|
||||
Intent killFloatingWindow = new Intent(getApplicationContext(),VideoPlayActivity.class);
|
||||
killFloatingWindow.putExtra("killFloat",true);
|
||||
|
||||
startActivity(killFloatingWindow);
|
||||
// TODO: only kill the notification if we no longer have a bound activity
|
||||
stopForeground(true);
|
||||
*/
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
playerNotificationManager.setPlayer(player);
|
||||
|
||||
// external Media control, Android Wear / Google Home etc.
|
||||
MediaSessionCompat mediaSession = new MediaSessionCompat(context, MEDIA_SESSION_TAG);
|
||||
mediaSession.setActive(true);
|
||||
playerNotificationManager.setMediaSessionToken(mediaSession.getSessionToken());
|
||||
MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mediaSession);
|
||||
mediaSessionConnector.setQueueNavigator(new TimelineQueueNavigator(mediaSession) {
|
||||
@Override
|
||||
public MediaDescriptionCompat getMediaDescription(Player player, int windowIndex) {
|
||||
return Video.getMediaDescription(context, currentVideo);
|
||||
}
|
||||
});
|
||||
mediaSessionConnector.setPlayer(player);
|
||||
|
||||
// Audio Focus
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(C.USAGE_MEDIA)
|
||||
.setContentType(C.CONTENT_TYPE_MOVIE)
|
||||
.build();
|
||||
player.setAudioAttributes(audioAttributes, true);
|
||||
|
||||
}
|
||||
|
||||
// pause playback on audio output change
|
||||
private class BecomingNoisyReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
|
||||
player.setPlayWhenReady(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* 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.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import net.schueller.peertube.helper.MetaDataHelper.getMetaString
|
||||
import net.schueller.peertube.model.Video.Companion.getMediaDescription
|
||||
import android.os.IBinder
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||
import android.content.IntentFilter
|
||||
import android.media.AudioManager
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||
import android.media.session.PlaybackState
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import net.schueller.peertube.network.UnsafeOkHttpClient
|
||||
import com.google.android.exoplayer2.source.MediaSource
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager.MediaDescriptionAdapter
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import net.schueller.peertube.activity.VideoPlayActivity
|
||||
import net.schueller.peertube.activity.VideoListActivity
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.webkit.URLUtil
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager.NotificationListener
|
||||
import net.schueller.peertube.R.drawable
|
||||
import net.schueller.peertube.R.string
|
||||
import net.schueller.peertube.model.Video
|
||||
import java.lang.Exception
|
||||
|
||||
class VideoPlayerService : Service() {
|
||||
|
||||
private val mBinder: IBinder = LocalBinder()
|
||||
@JvmField
|
||||
var player: ExoPlayer? = null
|
||||
private var currentVideo: Video? = null
|
||||
private var currentStreamUrl: String? = null
|
||||
private var currentStreamUrlIsHLS = false
|
||||
private var playerNotificationManager: PlayerNotificationManager? = null
|
||||
private val becomeNoisyIntentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||
private val myNoisyAudioStreamReceiver = BecomingNoisyReceiver()
|
||||
override fun onCreate() {
|
||||
Log.v(TAG, "onCreate...")
|
||||
super.onCreate()
|
||||
player = ExoPlayer.Builder(applicationContext)
|
||||
.setTrackSelector(DefaultTrackSelector(applicationContext))
|
||||
.build()
|
||||
|
||||
// Stop player if audio device changes, e.g. headphones unplugged
|
||||
player!!.addListener(object : Player.Listener {
|
||||
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||
if (playbackState.toLong() == PlaybackState.ACTION_PAUSE) { // this means that pause is available, hence the audio is playing
|
||||
Log.v(TAG, "ACTION_PLAY: $playbackState")
|
||||
registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter)
|
||||
}
|
||||
if (playbackState
|
||||
.toLong() == PlaybackState.ACTION_PLAY
|
||||
) { // this means that play is available, hence the audio is paused or stopped
|
||||
Log.v(TAG, "ACTION_PAUSE: $playbackState")
|
||||
safeUnregisterReceiver()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
|
||||
// Return this instance of VideoPlayerService so clients can call public methods
|
||||
val service: VideoPlayerService
|
||||
get() =// Return this instance of VideoPlayerService so clients can call public methods
|
||||
this@VideoPlayerService
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.v(TAG, "onDestroy...")
|
||||
if (playerNotificationManager != null) {
|
||||
playerNotificationManager!!.setPlayer(null)
|
||||
}
|
||||
//Was seeing an error when exiting the program about not unregistering the receiver.
|
||||
safeUnregisterReceiver()
|
||||
if (player != null) {
|
||||
player!!.release()
|
||||
player = null
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun safeUnregisterReceiver() {
|
||||
try {
|
||||
unregisterReceiver(myNoisyAudioStreamReceiver)
|
||||
} catch (e: Exception) {
|
||||
Log.e("VideoPlayerService", "attempted to unregister a non-registered service")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return mBinder
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
val context: Context = this
|
||||
Log.v(TAG, "onStartCommand...")
|
||||
return if (!URLUtil.isValidUrl(currentStreamUrl)) {
|
||||
Toast.makeText(context, "Invalid URL provided. Unable to play video.", Toast.LENGTH_SHORT).show()
|
||||
START_NOT_STICKY
|
||||
} else {
|
||||
playVideo()
|
||||
START_STICKY
|
||||
}
|
||||
}
|
||||
|
||||
fun setCurrentVideo(video: Video?) {
|
||||
Log.v(TAG, "setCurrentVideo...")
|
||||
currentVideo = video
|
||||
}
|
||||
|
||||
fun setCurrentStreamUrl(streamUrl: String, isHLS: Boolean) {
|
||||
Log.v(TAG, "setCurrentStreamUrl...$streamUrl")
|
||||
currentStreamUrlIsHLS = isHLS
|
||||
currentStreamUrl = streamUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current playback speed of the player.
|
||||
*
|
||||
* @return the current playback speed of the player.
|
||||
*///Playback speed control
|
||||
var playBackSpeed: Float
|
||||
get() = player!!.playbackParameters.speed
|
||||
set(speed) {
|
||||
Log.v(TAG, "setPlayBackSpeed...")
|
||||
player!!.playbackParameters = PlaybackParameters(speed)
|
||||
}
|
||||
|
||||
private fun playVideo() {
|
||||
val context: Context = this
|
||||
|
||||
// We need a valid URL
|
||||
Log.v(TAG, "playVideo...")
|
||||
|
||||
// Produces DataSource instances through which media data is loaded.
|
||||
val okhttpClientBuilder: okhttp3.OkHttpClient.Builder = if (!APIUrlHelper.useInsecureConnection(this)) {
|
||||
okhttp3.OkHttpClient.Builder()
|
||||
} else {
|
||||
UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder()
|
||||
}
|
||||
|
||||
// Create a data source factory.
|
||||
val dataSourceFactory: OkHttpDataSource.Factory = OkHttpDataSource.Factory(
|
||||
okhttpClientBuilder.build()
|
||||
)
|
||||
|
||||
// Create a progressive media source pointing to a stream uri.
|
||||
val mediaSource: MediaSource = if (currentStreamUrlIsHLS) {
|
||||
HlsMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)))
|
||||
} else {
|
||||
ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)))
|
||||
}
|
||||
|
||||
// Set the media source to be played.
|
||||
player!!.setMediaSource(mediaSource)
|
||||
|
||||
// Prepare the player.
|
||||
player!!.prepare()
|
||||
|
||||
// Auto play
|
||||
player!!.playWhenReady = true
|
||||
|
||||
//set playback speed to global default
|
||||
val sharedPref = getSharedPreferences(
|
||||
packageName + "_preferences",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
val speed = sharedPref.getString(getString(string.pref_video_speed_key), "1.0")!!.toFloat()
|
||||
playBackSpeed = speed
|
||||
|
||||
playerNotificationManager = PlayerNotificationManager.Builder(
|
||||
this,
|
||||
PLAYBACK_NOTIFICATION_ID,
|
||||
PLAYBACK_CHANNEL_ID,
|
||||
).setMediaDescriptionAdapter(
|
||||
object : MediaDescriptionAdapter {
|
||||
override fun getCurrentContentTitle(player: Player): CharSequence {
|
||||
return currentVideo!!.name
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
override fun createCurrentContentIntent(player: Player): PendingIntent? {
|
||||
val intent = Intent(context, VideoPlayActivity::class.java)
|
||||
intent.putExtra(VideoListActivity.EXTRA_VIDEOID, currentVideo!!.uuid)
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
else
|
||||
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
override fun getCurrentContentText(player: Player): CharSequence {
|
||||
return getMetaString(
|
||||
currentVideo!!.createdAt,
|
||||
currentVideo!!.views,
|
||||
baseContext
|
||||
)
|
||||
}
|
||||
|
||||
override fun getCurrentSubText(player: Player): CharSequence { return ""}
|
||||
|
||||
override fun getCurrentLargeIcon(
|
||||
player: Player,
|
||||
callback: PlayerNotificationManager.BitmapCallback
|
||||
): Bitmap? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
).setNotificationListener(
|
||||
object : NotificationListener {
|
||||
|
||||
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
|
||||
super.onNotificationPosted(notificationId, notification, ongoing)
|
||||
if (ongoing) // allow notification to be dismissed if player is stopped
|
||||
startForeground(notificationId, notification)
|
||||
else
|
||||
stopForeground(false)
|
||||
}
|
||||
|
||||
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
|
||||
super.onNotificationCancelled(notificationId, dismissedByUser)
|
||||
stopSelf()
|
||||
stopForeground(true)
|
||||
}
|
||||
}
|
||||
).setChannelNameResourceId(string.playback_channel_name)
|
||||
.setChannelDescriptionResourceId(string.playback_channel_description)
|
||||
.build()
|
||||
|
||||
playerNotificationManager!!.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
|
||||
playerNotificationManager!!.setSmallIcon(drawable.ic_logo_bw)
|
||||
|
||||
// don't show skip buttons in notification
|
||||
playerNotificationManager!!.setUseNextAction(false)
|
||||
playerNotificationManager!!.setUsePreviousAction(false)
|
||||
|
||||
playerNotificationManager!!.setPlayer(player)
|
||||
|
||||
// external Media control, Android Wear / Google Home etc.
|
||||
val mediaSession = MediaSessionCompat(context, MEDIA_SESSION_TAG)
|
||||
mediaSession.isActive = true
|
||||
playerNotificationManager!!.setMediaSessionToken(mediaSession.sessionToken)
|
||||
val mediaSessionConnector = MediaSessionConnector(mediaSession)
|
||||
mediaSessionConnector.setQueueNavigator(object : TimelineQueueNavigator(mediaSession) {
|
||||
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat {
|
||||
return getMediaDescription(currentVideo!!)
|
||||
}
|
||||
})
|
||||
mediaSessionConnector.setPlayer(player)
|
||||
|
||||
// Audio Focus
|
||||
val audioAttributes = AudioAttributes.Builder()
|
||||
.setUsage(C.USAGE_MEDIA)
|
||||
.setContentType(C.CONTENT_TYPE_MOVIE)
|
||||
.build()
|
||||
player!!.setAudioAttributes(audioAttributes, true)
|
||||
}
|
||||
|
||||
// pause playback on audio output change
|
||||
private inner class BecomingNoisyReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) {
|
||||
player!!.playWhenReady = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "VideoPlayerService"
|
||||
private const val MEDIA_SESSION_TAG = "peertube_player"
|
||||
private const val PLAYBACK_CHANNEL_ID = "playback_channel"
|
||||
private const val PLAYBACK_NOTIFICATION_ID = 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,9l6,6l6,-6"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,6L6,18"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M6,6L18,18"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,27 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M21,15v4a2,2 0,0 1,-2 2H5a2,2 0,0 1,-2 -2v-4"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M7,10l5,5l5,-5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,15L12,3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<vector android:height="52dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M13,19l9,-7l-9,-7l0,14z"
|
||||
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M2,19l9,-7l-9,-7l0,14z"
|
||||
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M4,15s1,-1 4,-1 5,2 8,2 4,-1 4,-1V3s-1,1 -4,1 -5,-2 -8,-2 -4,1 -4,1z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M4,22L4,15"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector android:height="52dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000" android:pathData="M6,4h4v16h-4z"
|
||||
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M14,4h4v16h-4z"
|
||||
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
|
@ -0,0 +1,7 @@
|
|||
<vector android:height="52dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M5,3l14,9l-14,9l0,-18z"
|
||||
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:viewportHeight="426.7"
|
||||
android:viewportWidth="426.7" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#ffffff" android:pathData="M0,64h256v42.7H0zM0,149.3h256V192H0zM0,234.7h170.7v42.7H0z"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M341.3,234.7v-85.4h-42.6v85.4h-85.4v42.6h85.4v85.4h42.6v-85.4h85.4v-42.6z"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<vector android:height="52dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M11,19l-9,-7l9,-7l0,14z"
|
||||
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M22,19l-9,-7l9,-7l0,14z"
|
||||
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
|
@ -0,0 +1,41 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,5m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M6,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M18,19m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M8.59,13.51L15.42,17.49"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15.41,6.51L8.59,10.49"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M4.93,4.93L19.07,19.07"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M10,15v4a3,3 0,0 0,3 3l4,-9L17,2L5.72,2a2,2 0,0 0,-2 1.7l-1.38,9a2,2 0,0 0,2 2.3zM17,2h2.67A2.31,2.31 0,0 1,22 4v7a2.31,2.31 0,0 1,-2.33 2L17,13"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M10,15v4a3,3 0,0 0,3 3l4,-9L17,2L5.72,2a2,2 0,0 0,-2 1.7l-1.38,9a2,2 0,0 0,2 2.3zM17,2h2.67A2.31,2.31 0,0 1,22 4v7a2.31,2.31 0,0 1,-2.33 2L17,13"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#555555"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M14,9V5a3,3 0,0 0,-3 -3l-4,9v11h11.28a2,2 0,0 0,2 -1.7l1.38,-9a2,2 0,0 0,-2 -2.3zM7,22H4a2,2 0,0 1,-2 -2v-7a2,2 0,0 1,2 -2h3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M14,9V5a3,3 0,0 0,-3 -3l-4,9v11h11.28a2,2 0,0 0,2 -1.7l1.38,-9a2,2 0,0 0,-2 -2.3zM7,22H4a2,2 0,0 1,-2 -2v-7a2,2 0,0 1,2 -2h3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#555555"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -1,175 +1,188 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".activity.MeActivity"
|
||||
android:orientation="vertical">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activity.MeActivity"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_me"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/tool_bar_me"
|
||||
android:id="@+id/appbar_me"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/tool_bar_me"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
<ScrollView
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:id="@+id/a_me_account_line"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/a_me_avatar"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:id="@+id/a_me_account_line"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/a_me_username"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/a_me_email"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/a_me_avatar"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"/>
|
||||
|
||||
<TextView
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/me_logout_button"
|
||||
android:id="@+id/a_me_logout"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
<LinearLayout
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/a_me_username"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/a_me_email"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title"/>
|
||||
|
||||
<TextView
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/me_logout_button"
|
||||
android:id="@+id/a_me_logout"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@android:color/darker_gray"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/a_me_playlist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:drawableStart="@drawable/ic_baseline_settings_24"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/playlist"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/a_me_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:drawableStart="@drawable/ic_baseline_settings_24"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_activity_settings"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/a_me_helpnfeedback"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:drawableStart="@drawable/ic_baseline_help_24"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/me_help_and_feedback_button"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@android:color/darker_gray" />
|
||||
</ScrollView>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/a_me_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:orientation="horizontal">
|
||||
<!-- <LinearLayout-->
|
||||
<!-- android:layout_marginBottom="0dp"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:orientation="vertical">-->
|
||||
|
||||
<TextView
|
||||
android:drawableStart="@drawable/ic_baseline_settings_24"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_activity_settings"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
</LinearLayout>
|
||||
<!-- <androidx.appcompat.widget.AppCompatTextView-->
|
||||
<!-- android:id="@+id/account_username"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="" />-->
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/a_me_helpnfeedback"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:drawableStart="@drawable/ic_baseline_help_24"
|
||||
android:drawablePadding="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/me_help_and_feedback_button"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <LinearLayout-->
|
||||
<!-- android:layout_marginBottom="0dp"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:orientation="vertical">-->
|
||||
|
||||
<!-- <androidx.appcompat.widget.AppCompatTextView-->
|
||||
<!-- android:id="@+id/account_username"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="" />-->
|
||||
|
||||
<!-- <androidx.appcompat.widget.AppCompatTextView-->
|
||||
<!-- android:id="@+id/account_email"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="" />-->
|
||||
<!-- </LinearLayout>-->
|
||||
<!-- <androidx.appcompat.widget.AppCompatTextView-->
|
||||
<!-- android:id="@+id/account_email"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="" />-->
|
||||
<!-- </LinearLayout>-->
|
||||
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
|
||||
tools:context=".activity.ServerAddressBookActivity"
|
||||
android:id="@+id/server_book">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_server_address_book"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/tool_bar_server_address_book"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:elevation="4dp"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<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_playlist"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
/>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -6,36 +6,31 @@
|
|||
android:keepScreenOn="true"
|
||||
tools:context="net.schueller.peertube.activity.VideoPlayActivity">
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<fragment android:name="net.schueller.peertube.fragment.VideoPlayerFragment"
|
||||
<fragment
|
||||
android:id="@+id/video_player_fragment"
|
||||
android:name="net.schueller.peertube.fragment.VideoPlayerFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="250dp" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/video_player_fragment"
|
||||
android:layout_marginTop="250dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/video_player_fragment"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/login_form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<fragment android:name="net.schueller.peertube.fragment.VideoMetaDataFragment"
|
||||
android:id="@+id/video_meta_data_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/video_meta_data_fragment"
|
||||
android:name="net.schueller.peertube.fragment.VideoMetaDataFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?android:colorBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:gravity="start"
|
||||
android:text="@string/video_meta_title_description"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_description_close_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:gravity="end"
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<TextView
|
||||
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:autoLink="web"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/description"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_privacy"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_privacy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_category"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_category"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_license"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_license"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_language"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_language"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_tags"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_tags"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
|
@ -1,364 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp">
|
||||
android:id="@+id/videoMetaFragment"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="0dp"
|
||||
android:contentDescription="@string/video_row_account_avatar"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sl_row_name"
|
||||
<!-- Related Videos -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/relatedVideosView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoMeta"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/sl_row_name"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoOwner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/videoMeta"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/moreButton"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
android:layout_marginStart="-16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_toEndOf="@+id/sl_row_name"
|
||||
android:background="@null"
|
||||
android:contentDescription="@string/descr_overflow_button"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/video_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/videoOwner"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="65dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/video_thumbs_up"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_thumbs_up_total"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="65dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/video_thumbs_down"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_thumbs_down_total"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="65dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/video_share"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_share_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_share"
|
||||
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="65dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/video_download"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_download_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_download"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/video_actions"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:autoLink="web"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/description"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_privacy"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_privacy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
android:lines="2"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_category"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_category"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
android:lines="2"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_license"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_license"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem"
|
||||
android:lines="2"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_language"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_language"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
android:lines="2"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_tags"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<Space
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_tags"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,94 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6dp"
|
||||
android:padding="6dp"
|
||||
android:id="@+id/video_title_block"
|
||||
>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/video_comments_title_wrapper"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_comments_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/video_comments_title"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_comments_total_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_toEndOf="@+id/video_comments_title"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:clickable="false"
|
||||
android:contentDescription="@string/video_meta_show_description"
|
||||
android:src="@drawable/ic_chevron_down"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/video_highlighted_comment_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/video_comments_title_wrapper">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/video_highlighted_avatar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_highlighted_comment"
|
||||
android:layout_toEndOf="@+id/video_highlighted_avatar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,412 @@
|
|||
<?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="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent">
|
||||
|
||||
<!-- Video Title Block -->
|
||||
<RelativeLayout
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:id="@+id/video_title_block"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp"
|
||||
android:paddingTop="6dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/video_open_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:clickable="false"
|
||||
android:contentDescription="@string/video_meta_show_description"
|
||||
android:src="@drawable/ic_chevron_down"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoMeta"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/video_open_description"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- video actions -->
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/video_actions_block"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/video_title_block"
|
||||
android:paddingBottom="6dp"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/video_actions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:paddingStart="18dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="70dp"
|
||||
android:id="@+id/video_thumbs_up_wrapper"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_thumbs_up"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:src="@drawable/ic_thumbs_up"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_thumbs_up_total"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="70dp"
|
||||
android:id="@+id/video_thumbs_down_wrapper"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_thumbs_down"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:src="@drawable/ic_thumbs_down"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_thumbs_down_total"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="70dp"
|
||||
android:id="@+id/video_share_wrapper"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_share"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:src="@drawable/ic_share_2"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_share_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_share"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="70dp"
|
||||
android:id="@+id/video_download_wrapper"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_download"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:src="@drawable/ic_download"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_download_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_meta_button_download"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="70dp"
|
||||
android:id="@+id/video_add_to_playlist_wrapper"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_add_to_playlist"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:src="@drawable/ic_playlist_add"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_add_to_playlist_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_add_to_playlist"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="70dp"
|
||||
android:id="@+id/video_block_wrapper"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_block"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:src="@drawable/ic_slash"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/vvideo_block_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_block"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="70dp"
|
||||
android:id="@+id/video_flag_wrapper"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/video_flag"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:src="@drawable/ic_flag"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/vvideo_flag_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_flag"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<TextView
|
||||
android:layout_below="@+id/video_actions_block"
|
||||
android:id="@+id/video_action_block_line"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:background="?android:colorEdgeEffect"
|
||||
android:height="1dp"
|
||||
android:gravity="center_horizontal"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/video_account_block"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/video_action_block_line"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="6dp">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@string/video_row_account_avatar"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="6dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/video_creator_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoOwner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoBy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoOwnerSubscribers"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoOwnerSubscribeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:gravity="end"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text=""
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" />
|
||||
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:id="@+id/moreButton"-->
|
||||
<!-- android:layout_width="45dp"-->
|
||||
<!-- android:layout_height="45dp"-->
|
||||
<!-- android:layout_marginStart="-16dp"-->
|
||||
<!-- android:layout_marginTop="16dp"-->
|
||||
<!-- android:layout_marginEnd="0dp"-->
|
||||
<!-- android:layout_toEndOf="@+id/sl_row_name"-->
|
||||
<!-- android:background="@null"-->
|
||||
<!-- android:contentDescription="@string/descr_overflow_button"-->
|
||||
<!-- android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />-->
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_below="@+id/video_account_block"
|
||||
android:id="@+id/video_account_block_line"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:background="?android:colorEdgeEffect"
|
||||
android:height="1dp"
|
||||
android:gravity="center_horizontal"/>
|
||||
</RelativeLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,43 @@
|
|||
<?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/video_name"
|
||||
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.Title"
|
||||
tools:text="@tools:sample/lorem"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
card_view:layout_constraintTop_toBottomOf="@id/video_name"
|
||||
card_view:layout_constraintStart_toStartOf="parent"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead"
|
||||
tools:text="@tools:sample/lorem"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -8,7 +8,7 @@
|
|||
android:background="#CC000000"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="vertical"
|
||||
tools:targetApi="28">
|
||||
tools:targetApi="32">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/exo_more_button"
|
||||
|
@ -18,14 +18,14 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/exo_more"
|
||||
android:layout_width="18dp"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:paddingTop="12dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:textColor="#FFBEBEBE"
|
||||
android:textSize="12sp" />
|
||||
android:textSize="18sp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -43,27 +43,63 @@
|
|||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<ImageButton
|
||||
android:id="@id/exo_rew"
|
||||
style="@style/ExoMediaButton.Rewind" />
|
||||
|
||||
android:layout_width="72sp"
|
||||
android:layout_height="52sp"
|
||||
android:layout_gravity="start"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/exo_controls_rewind_description"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_rewind" />
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<ImageButton
|
||||
android:id="@id/exo_repeat_toggle"
|
||||
style="@style/ExoMediaButton" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@id/exo_play"
|
||||
style="@style/ExoMediaButton.Play" />
|
||||
android:contentDescription="@string/exo_controls_play_description"
|
||||
android:src="@drawable/ic_play"
|
||||
android:layout_height="52sp"
|
||||
android:layout_width="72sp"
|
||||
android:scaleType="center"
|
||||
android:background="@android:color/transparent"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@id/exo_pause"
|
||||
style="@style/ExoMediaButton.Pause" />
|
||||
|
||||
android:contentDescription="@string/exo_controls_pause_description"
|
||||
android:src="@drawable/ic_pause"
|
||||
android:layout_height="52sp"
|
||||
android:layout_width="72sp"
|
||||
android:scaleType="center"
|
||||
android:background="@android:color/transparent"
|
||||
/>
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<ImageButton
|
||||
android:id="@id/exo_ffwd"
|
||||
style="@style/ExoMediaButton.FastForward" />
|
||||
|
||||
android:layout_width="72sp"
|
||||
android:layout_height="52sp"
|
||||
android:layout_gravity="end"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/exo_controls_fastforward_description"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fast_forward" />
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
@ -86,9 +122,29 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:layout_gravity="center"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:textColor="#FFBEBEBE"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="2dp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="#BABABA"
|
||||
android:textSize="14sp"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/player_time_seperator" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/exo_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:textColor="#BABABA"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<View
|
||||
|
@ -96,18 +152,6 @@
|
|||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/exo_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:layout_gravity="center"
|
||||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp"
|
||||
android:textColor="#FFBEBEBE"
|
||||
android:textSize="14sp" />
|
||||
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/exo_fullscreen_button"
|
||||
|
@ -117,13 +161,13 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/exo_fullscreen"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:textColor="#FFBEBEBE"
|
||||
android:textSize="12sp" />
|
||||
android:textSize="18sp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -146,20 +190,20 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:visibility="gone"
|
||||
android:id="@+id/exo_torrent_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!-- <LinearLayout-->
|
||||
<!-- android:visibility="gone"-->
|
||||
<!-- android:id="@+id/exo_torrent_status"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content">-->
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/torrent_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="false"
|
||||
android:max="100" />
|
||||
<!-- <ProgressBar-->
|
||||
<!-- android:id="@+id/torrent_progress"-->
|
||||
<!-- style="?android:attr/progressBarStyleHorizontal"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:indeterminate="false"-->
|
||||
<!-- android:max="100" />-->
|
||||
|
||||
</LinearLayout>
|
||||
<!-- </LinearLayout>-->
|
||||
|
||||
</LinearLayout>
|
|
@ -345,7 +345,7 @@
|
|||
<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="server_selection_filter_hint">تنقيح القائمة</string>
|
||||
<string name="pref_background_behavior">إعدادات التشغيل في الخلفية</string>
|
||||
<string name="pref_background_stop">إيقاف تشغيل الكل</string>
|
||||
<string name="pref_background_audio">تابع تشغيل الصوت في الخلفية</string>
|
||||
|
@ -361,4 +361,9 @@
|
|||
<string name="server_book_add_save_button">حفظ</string>
|
||||
<string name="pref_title_accept_insecure">تعطيل التحقق من شهادة SSL</string>
|
||||
<string name="pref_description_accept_insecure">تجاهل الاتصالات غير الآمنة. استخدم هذا فقط إذا كنت تعرف الخادم الذي تتصل به. يتطلب إعادة تشغيل التطبيق.</string>
|
||||
<string name="pref_title_video_speed">سرعة التشغيل الافتراضية</string>
|
||||
<string name="pref_description_video_speed">حدد سرعة تشغيل الفيديو العامة</string>
|
||||
<string name="action_bar_title_address_book">دفتر العناوين</string>
|
||||
<string name="video_get_full_description_failed">تعذر جلب وصف الفيديو الكامل</string>
|
||||
<string name="video_description_read_more">اقرأ المزيد</string>
|
||||
</resources>
|
|
@ -276,7 +276,7 @@
|
|||
<string name="video_login_required_for_service">এই সেবা ব্যবহারের জন্য লগ ইন করো</string>
|
||||
<string name="video_meta_button_share">শেয়ার</string>
|
||||
<string name="video_meta_button_download">ডাউনলোড</string>
|
||||
<string name="video_meta_button_privacy">Privacy</string>
|
||||
<string name="video_meta_button_privacy"></string>
|
||||
<string name="video_meta_button_category">বিভাগ</string>
|
||||
<string name="video_meta_button_license">অনুমতিপত্র</string>
|
||||
<string name="video_meta_button_language">ভাষা</string>
|
||||
|
@ -295,7 +295,7 @@
|
|||
<string name="account_about_description">বিস্তারিত:</string>
|
||||
<string name="account_about_joined">যুক্ত হয়েছে:</string>
|
||||
<string name="api_error">কিছু সমস্যা হয়েছে, অনুগ্রহ করে পরে চেষ্টা করো!</string>
|
||||
<string name="action_set_url">সার্ভার পছন্দ করো</string>
|
||||
<string name="action_set_url">সার্ভার পছন্দ ক</string>
|
||||
<string name="server_selection_signup_allowed_yes">হ্যা</string>
|
||||
<string name="server_selection_signup_allowed_no">না</string>
|
||||
<string name="server_selection_peertube_server_url">পিয়ারটিউব সার্ভার URL</string>
|
||||
|
@ -333,7 +333,7 @@
|
|||
<string name="authentication_login_success">লগইন হয়েছে</string>
|
||||
<string name="bn_rBD">বাংলা (বাংলাদেশ)</string>
|
||||
<string name="clear_search_history_prompt">আপনি কি পুরোপুরি সার্চ ইতিহাস মুছে ফেলতে চান\?</string>
|
||||
<string name="clear_search_history">সার্চ ইতিহাস মুছে ফেলো</string>
|
||||
<string name="clear_search_history">সার্চ ইতিহাস মুছে ফেলুন</string>
|
||||
<string name="pref_background_behavior_summary">চালু ভিডিও কি করবে যখন পেছনে যাবে</string>
|
||||
<string name="settings_permissions_error_float">অ্যান্ড্রয়েড সেটিং এ পিকচার ইন পিকচার পারমিশন বন্ধ আছে এই আয়াপ এ</string>
|
||||
<string name="settings_api_error_float">অ্যান্ড্রয়েড ভার্সন ভাসমান ভিডিও সাপোর্ট করে</string>
|
||||
|
@ -355,11 +355,16 @@
|
|||
<string name="server_selection_filter_hint">ফিল্টার তালিকা</string>
|
||||
<string name="pref_insecure_confirm_message">তুমি থোরিয়াম এর সকল এসএসএল প্রত্যয়ন বৈধতা নিষ্ক্রিয় করতে যাচ্ছ। এটি নিষ্ক্রিয় করা খুবই বিপজ্জনক হতে পারে যদি পিয়ারটিউব সার্ভারটি তোমার নিয়ন্ত্রণে না থাকে, কারণ একটি ম্যান-ইন-দ্য-মিডল আক্রমণ তোমার অজান্তেই অন্য সার্ভারে ট্রাফিক পরিচালনা করতে পারে। একজন আক্রমণকারী পাসওয়ার্ড এবং অন্যান্য ব্যক্তিগত তথ্য রেকর্ড করতে পারে।</string>
|
||||
<string name="pref_description_accept_insecure">অনিরাপদ সংযোগ উপেক্ষা করো। তুমি যে সার্ভারের সাথে সংযুক্ত হচ্ছো তা জানলে তবেই এটি ব্যবহার করো। অ্যাপ পুনর্সূচনা প্রয়োজন।</string>
|
||||
<string name="pref_title_accept_insecure">এসএসএল সার্টিফিকেট চেক নিষ্ক্রিয় করো</string>
|
||||
<string name="pref_title_accept_insecure">এসএসএল সার্টিফিকেট চেক নিষ্ক্রিয় ক</string>
|
||||
<string name="pref_insecure_confirm_yes">হ্যাঁ</string>
|
||||
<string name="pref_insecure_confirm_no">না</string>
|
||||
<string name="pref_insecure_confirm_title">সতর্কতা!</string>
|
||||
<string name="settings_activity_advanced_category_title">উন্নত</string>
|
||||
<string name="server_book_add_save_button">সংরক্ষন</string>
|
||||
<string name="video_list_live_marker">লাইভ</string>
|
||||
<string name="action_bar_title_address_book">ঠিকানা বই</string>
|
||||
<string name="pref_title_video_speed">সহজাত গতি</string>
|
||||
<string name="pref_description_video_speed">সর্বজনীন গতি নির্ধারণ করো</string>
|
||||
<string name="video_description_read_more">আরও পড়ুন</string>
|
||||
<string name="video_get_full_description_failed">ভিডিওর বিবরণ খুঁজে পাওয়া যায় নি</string>
|
||||
</resources>
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="title_activity_login">Accés</string>
|
||||
<string name="action_bar_title_search">Cerca</string>
|
||||
<string name="action_sign_in">Accés</string>
|
||||
<string name="action_sign_in_short">Accés</string>
|
||||
<string name="bottom_nav_title_discover">Inici</string>
|
||||
<string name="bottom_nav_title_trending">Tendències</string>
|
||||
<string name="bottom_nav_title_recent">Recent</string>
|
||||
<string name="bottom_nav_title_local">Local</string>
|
||||
<string name="bottom_nav_title_subscriptions">Subscripcions</string>
|
||||
<string name="bottom_nav_title_account">Compte</string>
|
||||
<string name="meta_data_views">" Vistes"</string>
|
||||
<string name="video_row_video_thumbnail">Miniatura de vídeo</string>
|
||||
<string name="video_row_account_avatar">Avatar del compte</string>
|
||||
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
|
||||
<string name="search_hint">Cerca a PerrTube</string>
|
||||
<string name="title_activity_search">Cerca</string>
|
||||
<string name="descr_overflow_button">Més</string>
|
||||
<string name="menu_share">Comparteix</string>
|
||||
<string name="invalid_url">URL no vàlida</string>
|
||||
<string name="pref_title_dark_mode">Mode fosc</string>
|
||||
<string name="pref_title_app_theme">Tema de l\'aplicació</string>
|
||||
<string name="pref_description_app_theme">Reiniciar perquè s\'activi el tema.</string>
|
||||
<string name="pref_title_torrent_player">Reproductor de vídeo Torrent</string>
|
||||
<string name="pref_title_license">Llicència</string>
|
||||
<string name="prompt_email">Email / Nom d\'usuari</string>
|
||||
<string name="title_activity_settings">Configuració</string>
|
||||
<string name="prompt_server">Servidor</string>
|
||||
<string name="prompt_password">Contrasenya</string>
|
||||
<string name="no_data_available">Sense resultats</string>
|
||||
<string name="error_invalid_password">La contrasenya és massa curta</string>
|
||||
<string name="error_field_required">Aquest camp és obligatori</string>
|
||||
<string name="action_bar_title_settings">Configuració</string>
|
||||
<string name="action_bar_title_logout">Tanca</string>
|
||||
<string name="action_bar_title_account">Compte</string>
|
||||
<string name="error_invalid_email">Aquest email no és vàlid</string>
|
||||
<string name="error_incorrect_password">La contrasenya és incorrecta</string>
|
||||
<string name="permission_rationale">Dona permís al contacte per completar l\'email.</string>
|
||||
<string name="pref_description_dark_mode">Reiniciar perquè s\'apliqui el mode obscur</string>
|
||||
<string name="pref_description_torrent_player">Reproducció de vídeo per flux de torrent. Requereix permisos d\'emmagatzematge. (Alfa, no estable!)</string>
|
||||
</resources>
|
|
@ -356,4 +356,9 @@
|
|||
<string name="pref_title_accept_insecure">SSL-Zertifikatsprüfung deaktivieren</string>
|
||||
<string name="pref_description_accept_insecure">Unsichere Verbindungen ignorieren. Verwenden Sie dies nur, wenn Sie den Server kennen, mit dem Sie sich verbinden. Erfordert einen Neustart der Anwendung.</string>
|
||||
<string name="video_list_live_marker">LIVE</string>
|
||||
<string name="pref_title_video_speed">Standard-Wiedergabegeschwindigkeit</string>
|
||||
<string name="pref_description_video_speed">Wählen Sie die globale Videowiedergabegeschwindigkeit</string>
|
||||
<string name="action_bar_title_address_book">Adressbuch</string>
|
||||
<string name="video_get_full_description_failed">Die vollständige Videobeschreibung konnte nicht abgerufen werden</string>
|
||||
<string name="video_description_read_more">Mehr lesen</string>
|
||||
</resources>
|
|
@ -118,13 +118,13 @@
|
|||
<string name="me_logout_button">Cerrar sesión</string>
|
||||
<string name="server_book_valid_url_is_required">Una URL válida es requerida</string>
|
||||
<string name="server_book_label_is_required">La etiqueta de servidor es requerida</string>
|
||||
<string name="rsl">Ruso (Lenguaje de señas)</string>
|
||||
<string name="rsl">Lengua de señas rusa</string>
|
||||
<string name="ru">Ruso</string>
|
||||
<string name="ro">Romano</string>
|
||||
<string name="ro">Rumano</string>
|
||||
<string name="pt">Portugués</string>
|
||||
<string name="pl">Polaco</string>
|
||||
<string name="no">Noruego</string>
|
||||
<string name="pks">Pakistaní (Lenguaje de señas)</string>
|
||||
<string name="pks">Lengua de señas de Pakistán</string>
|
||||
<string name="fsl">Francés (Lenguaje de señas)</string>
|
||||
<string name="fr">Francés</string>
|
||||
<string name="fi">Finlandés</string>
|
||||
|
@ -193,4 +193,111 @@
|
|||
<string name="pref_description_back_pause">Pausa la reproducción de fondo al presionar atrás durante la reproducción de vídeo.</string>
|
||||
<string name="video_list_live_marker">EN VIVO</string>
|
||||
<string name="co">Corso</string>
|
||||
<string name="nv">Navajo</string>
|
||||
<string name="sl">Esloveno</string>
|
||||
<string name="fj">Fiyiano</string>
|
||||
<string name="cv">Chuvasio</string>
|
||||
<string name="cr">Cree</string>
|
||||
<string name="nl">Holandés</string>
|
||||
<string name="dz">Dzongkha</string>
|
||||
<string name="et">Estonio</string>
|
||||
<string name="ee">Ewe</string>
|
||||
<string name="hi">Hindi</string>
|
||||
<string name="ho">Hiri Motu</string>
|
||||
<string name="hu">Húngaro</string>
|
||||
<string name="is">Islandés</string>
|
||||
<string name="ga">Irlandés</string>
|
||||
<string name="it">Italiano</string>
|
||||
<string name="kr">Kanuri</string>
|
||||
<string name="mg">Malgache</string>
|
||||
<string name="ml">Malayalam</string>
|
||||
<string name="mt">Maltés</string>
|
||||
<string name="gv">Manés</string>
|
||||
<string name="mi">Maori</string>
|
||||
<string name="pa">Panyabí</string>
|
||||
<string name="fa">Persa</string>
|
||||
<string name="rm">Romanche</string>
|
||||
<string name="sr">Serbio</string>
|
||||
<string name="ta">Tamil</string>
|
||||
<string name="te">Telugu</string>
|
||||
<string name="bo">Tibetano</string>
|
||||
<string name="ti">Tigriña</string>
|
||||
<string name="ts">Tsonga</string>
|
||||
<string name="tn">Tswana</string>
|
||||
<string name="tr">Turco</string>
|
||||
<string name="tk">Turcomano</string>
|
||||
<string name="tw">Twi</string>
|
||||
<string name="ug">Uigur</string>
|
||||
<string name="mn">Mongol</string>
|
||||
<string name="lg">Ganda</string>
|
||||
<string name="xh">Xhosa</string>
|
||||
<string name="th">Tailandés</string>
|
||||
<string name="lb">Luxemburgués</string>
|
||||
<string name="jv">Javanés</string>
|
||||
<string name="kw">Córnico</string>
|
||||
<string name="dv">Dhivehi</string>
|
||||
<string name="fo">Feroés</string>
|
||||
<string name="ff">Fula</string>
|
||||
<string name="gl">Gallego</string>
|
||||
<string name="ka">Georgiano</string>
|
||||
<string name="de">Alemán</string>
|
||||
<string name="gsg">Lengua de señas alemana</string>
|
||||
<string name="gn">Guaraní</string>
|
||||
<string name="gu">Guyaratí</string>
|
||||
<string name="ht">Haitiano</string>
|
||||
<string name="ha">Hausa</string>
|
||||
<string name="he">Hebreo</string>
|
||||
<string name="hz">Herero</string>
|
||||
<string name="ig">Igbo</string>
|
||||
<string name="id">Indonesio</string>
|
||||
<string name="iu">Inuktitut</string>
|
||||
<string name="ik">Inupiaq</string>
|
||||
<string name="ja">Japonés</string>
|
||||
<string name="jsl">Lengua de señas japonesa</string>
|
||||
<string name="kl">Kalaallisut</string>
|
||||
<string name="kn">Kannada</string>
|
||||
<string name="ks">Cachemiro</string>
|
||||
<string name="tlh">Klingon</string>
|
||||
<string name="ko">Coreano</string>
|
||||
<string name="ku">Kurdo</string>
|
||||
<string name="lo">Lao</string>
|
||||
<string name="lv">Letón</string>
|
||||
<string name="kk">Kazajo</string>
|
||||
<string name="km">Jémer</string>
|
||||
<string name="ki">Kikuyu</string>
|
||||
<string name="mk">Macedonio</string>
|
||||
<string name="nb">Noruego bokmål</string>
|
||||
<string name="nn">Noruego nynorsk</string>
|
||||
<string name="oc">Occitano</string>
|
||||
<string name="oj">Ojibwa</string>
|
||||
<string name="ps">Pastún</string>
|
||||
<string name="qu">Quechua</string>
|
||||
<string name="sdl">Lenguaje de señas de Arabia Saudita</string>
|
||||
<string name="sk">Eslovaco</string>
|
||||
<string name="so">Somalí</string>
|
||||
<string name="sfs">Lengua de señas sudafricana</string>
|
||||
<string name="es">Español</string>
|
||||
<string name="sv">Sueco</string>
|
||||
<string name="swl">Lengua de señas sueca</string>
|
||||
<string name="tl">Tagalo</string>
|
||||
<string name="ty">Tahitiano</string>
|
||||
<string name="tg">Tayiko</string>
|
||||
<string name="to">Tonga (Islas Tonga)</string>
|
||||
<string name="uk">Ucraniano</string>
|
||||
<string name="ur">Urdu</string>
|
||||
<string name="uz">Uzbeko</string>
|
||||
<string name="ve">Venda</string>
|
||||
<string name="vi">Vietnamita</string>
|
||||
<string name="wa">Valón</string>
|
||||
<string name="cy">Galés</string>
|
||||
<string name="fy">Frisón occidental</string>
|
||||
<string name="wo">Wolof</string>
|
||||
<string name="yi">Yidis</string>
|
||||
<string name="yo">Yoruba</string>
|
||||
<string name="zu">Zulú</string>
|
||||
<string name="pref_title_video_speed">Velocidad de reproducción por defecto</string>
|
||||
<string name="pref_description_video_speed">Seleccione la velocidad global de reproducción de vídeo</string>
|
||||
<string name="ms">Malayo (macrolengua)</string>
|
||||
<string name="gd">Gaélico escocés</string>
|
||||
<string name="sh">Serbo-croata</string>
|
||||
</resources>
|
|
@ -20,25 +20,25 @@
|
|||
<string name="bottom_nav_title_local">محلی</string>
|
||||
<string name="bottom_nav_title_subscriptions">اشتراکها</string>
|
||||
<string name="bottom_nav_title_account">حساب</string>
|
||||
<string name="meta_data_views">" بازدیدها"</string>
|
||||
<string name="video_row_video_thumbnail">تصویر ویدئو</string>
|
||||
<string name="video_row_account_avatar">آواتار حساب</string>
|
||||
<string name="search_hint">جستجو در پیرتیوب</string>
|
||||
<string name="title_activity_search">جستجو</string>
|
||||
<string name="meta_data_views">" نمایش"</string>
|
||||
<string name="video_row_video_thumbnail">بندانگشتی ویدیو</string>
|
||||
<string name="video_row_account_avatar">چهرک حساب</string>
|
||||
<string name="search_hint">جستوجوی پیرتیوب</string>
|
||||
<string name="title_activity_search">جستوجو</string>
|
||||
<string name="no_data_available">بدون نتیجه</string>
|
||||
<string name="descr_overflow_button">بیشتر</string>
|
||||
<string name="descr_overflow_button">بیشتر</string>
|
||||
<string name="menu_share">همرسانی</string>
|
||||
<string name="invalid_url">نشانی نامعتبر</string>
|
||||
<string name="invalid_url">نشانی نامعتبر.</string>
|
||||
<string name="pref_title_dark_mode">حالت تاریک</string>
|
||||
<string name="pref_description_dark_mode">برای اعمال حالت تاریک، برنامه را از اول راهاندازی کنید.</string>
|
||||
<string name="pref_title_app_theme">سبک برنامه</string>
|
||||
<string name="pref_description_app_theme">برای اعمال سبک، برنامه را از اول راهاندازی کنید</string>
|
||||
<string name="pref_title_torrent_player">پخشکننده ویدئوی تورنت</string>
|
||||
<string name="pref_title_app_theme">زمینهٔ کاره</string>
|
||||
<string name="pref_description_app_theme">برای تأثیر گذاشتن زمینه، کاره را دوباره آغاز کنید.</string>
|
||||
<string name="pref_title_torrent_player">پخشکنندهٔ ویدیوی تورنت</string>
|
||||
<string name="pref_title_license">پروانه</string>
|
||||
<string name="pref_title_version">نسخه</string>
|
||||
<string name="pref_title_show_nsfw">محتوا NSFW</string>
|
||||
<string name="pref_title_version">نگارش</string>
|
||||
<string name="pref_title_show_nsfw">محتوای NSFW</string>
|
||||
<string name="pref_description_show_nsfw">نمایش محتوای NSFW</string>
|
||||
<string name="pref_language">صافی زبان</string>
|
||||
<string name="pref_language">پالایهٔ زبان</string>
|
||||
<string name="pref_title_peertube_server">کارساز پیرتیوب</string>
|
||||
<string name="pref_title_background_play">پخش در پسزمینه</string>
|
||||
<string name="menu_video_more_report">گزارش</string>
|
||||
|
@ -89,7 +89,7 @@
|
|||
<string name="fr">فرانسوی</string>
|
||||
<string name="fi">فنلاندی</string>
|
||||
<string name="en">انگلیسی</string>
|
||||
<string name="as"/>
|
||||
<string name="as">آسامی</string>
|
||||
<string name="da">دانمارکی</string>
|
||||
<string name="zh">چینی</string>
|
||||
<string name="bg">بلغاری</string>
|
||||
|
@ -98,23 +98,109 @@
|
|||
<string name="az">آذربایجانی</string>
|
||||
<string name="hy">ارمنی</string>
|
||||
<string name="ar">عربی</string>
|
||||
<string name="pref_description_background_play">در صورت فعال بودن، پخش ویدئو در پسزمینه ادامه مییابد.</string>
|
||||
<string name="pref_description_language">به جای نشان دادن همه ویدئه تحت همه زبانها، یک زبان برای ویدئو انتخاب کنید.</string>
|
||||
<string name="pref_description_background_play">اگر به کار افتاده باشد، پخش ویدیو را در پسزمینه ادامه میدهد.</string>
|
||||
<string name="pref_description_language">به جای نمایش تمامی ویدیوها به همهٔ زبانها، زبانی برای ویدیو برگزینید.</string>
|
||||
<string name="pref_description_license">
|
||||
\n<b>پروانه عمومی همگانی آفرو نسخه ۳ AGPLv3</b>
|
||||
\n
|
||||
\nمجوزهای این پروانه که قویترین پروانه کپیلفت است مشروط به دردسترس قرار دادن کامل کد منبع کارهای تحت پروانه و نسخههای تغییریافتهشان است که شامل کارهای بزرگتری که تحت همین پروانه × از این کار استفاده میکنند میشود. تذکر پروانه و کپیرایت باید محفوظ بماند. مشارکتکنندگان باید واگذاری حقوق پتنت را اعلام کنند. وقتی نسخه تغییر یافته برای ارائه خدمت روی شبکه استفاده شود، کد منبع نسخه تغییر یافته بایستی به صورت کامل دردسترس قرار بگیرد.</string>
|
||||
<string name="pref_description_torrent_player">پخش ویدئو از طریق جریان تورنت. این ویژگی، نیازمند مجور دسترسی به فضای ذخیرهسازی است. (آلفا، ناپایدار!)</string>
|
||||
<string name="pref_description_torrent_player">پخش ویدیو با جریان تورنت. این ویژگی، نیازمند اجازهٔ ذخیرهسازی است. (آلفا، ناپایدار!)</string>
|
||||
<string name="bottom_nav_title_discover">نوار ناوبری پایین</string>
|
||||
<string name="settings_api_error_float">نسخه اندروید از ویدئوی شناور پشتیبانی نمی کند</string>
|
||||
<string name="pref_background_behavior">پیکربندی پخش در پس زمینه</string>
|
||||
<string name="pref_background_float">پخش ویدیو را در پنجره شناور ادامه دهید</string>
|
||||
<string name="pref_background_stop">تمام پخش را متوقف کنید</string>
|
||||
<string name="pref_background_audio">به عنوان جریان صوتی پس زمینه ادامه دهید</string>
|
||||
<string name="pref_description_language_app">انتخاب زبان برای رابط برنامه برای اعمال تغییرات ، برنامه را مجدداً راه اندازی کنید.</string>
|
||||
<string name="settings_api_error_float">نگارش اندروید از ویدیوی شناور پشتیبانی نمیکند</string>
|
||||
<string name="pref_background_behavior">پیکربندی پخش پسزمینه</string>
|
||||
<string name="pref_background_float">ادامهٔ پخش ویدیو در پنجرهٔ شناور</string>
|
||||
<string name="pref_background_stop">توقّف تمامی پخش</string>
|
||||
<string name="pref_background_audio">ادامه به شکل جریان صوتی پسزمینه</string>
|
||||
<string name="pref_description_language_app">گزینش زبان رابط برنامه.برای تأثیر گذاشتن تغییرات، کاره را دوباره آغاز کنید.</string>
|
||||
<string name="pref_language_app">زبان برنامه</string>
|
||||
<string name="pref_description_back_pause">هنگام فشار دادن به عقب در حین پخش ویدئو ، پخش پس زمینه را متوقف کنید.</string>
|
||||
<string name="pref_title_back_pause">دکمه پشت مکث</string>
|
||||
<string name="title_activity_url_video_play">فعالیت پخش ویدئو Url</string>
|
||||
<string name="permission_rationale">برای تکمیل ایمیل مجوز تماس بگیرید.</string>
|
||||
<string name="pref_description_back_pause">مکث پخش پسزمینه هنگام فشردن بازگشت حین پخش ویدیو.</string>
|
||||
<string name="pref_title_back_pause">مکث با دکمهٔ بازگشت</string>
|
||||
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
|
||||
<string name="permission_rationale">اعطای اجازهٔ آشنا برای تکمیل رایانامه.</string>
|
||||
<string name="pref_title_video_speed">سرعت پخش پیشگزیده</string>
|
||||
<string name="pref_description_video_speed">گزینش سرعت پخش ویدیوی عمومی</string>
|
||||
<string name="network_error">خطای دسترسی به شبکه. لطفاً اتّصالتان را بررسی کنید</string>
|
||||
<string name="login_current_server_hint">کارساز کنونی</string>
|
||||
<string name="server_book_add_save_button">ذخیره</string>
|
||||
<string name="authentication_token_refresh_failed">نتوانست ژتون را تازه کند</string>
|
||||
<string name="clear_search_history_prompt">می خواهید تاریخچهٔ جستوجو را برای همیشه حذف کنید؟</string>
|
||||
<string name="server_selection_video_totals" formatted="false">ویدیوها: %s* ویدیوهای محلّی: %s</string>
|
||||
<string name="pref_description_accept_insecure">چشمپوشی از اتّصالهای ناامن. فقط اگر کارسازی که به آن وصل میشوید را میشناسید، از این گزینه استفاده کنید. نیاز به آغاز دوبارهٔ کاره.</string>
|
||||
<string name="video_description_read_more">خواندن بیشتر</string>
|
||||
<string name="authentication_login_success">وارد شده</string>
|
||||
<string name="pref_title_accept_insecure">از کار انداختن بررسی گواهینامهٔ SSL</string>
|
||||
<string name="authentication_token_refresh_success">ژتون تازه شد</string>
|
||||
<string name="menu_video_options_quality_automated">خودکار</string>
|
||||
<string name="server_selection_nsfw_instance">نمونهٔ نمبک</string>
|
||||
<string name="settings_activity_about_category_title">درباره</string>
|
||||
<string name="title_activity_select_server">کارساز جستوجو</string>
|
||||
<string name="video_get_full_description_failed">گرفتن شرح کامل ویدیو شکست خورد</string>
|
||||
<string name="server_book_del_alert_title">برداشتن کارساز</string>
|
||||
<string name="video_list_live_marker">زنده</string>
|
||||
<string name="server_book_label_is_required">نیازمند برچسب کارساز</string>
|
||||
<string name="pref_insecure_confirm_yes">بله</string>
|
||||
<string name="pref_insecure_confirm_message">داید تمامی تأییدیههای گواهینامهٔ SSL را در توریوم از کار میاندازید. از کار انداختن این گزینه هنگامی که کارساز تحت کنترلتان ینست، میتواند بسیار خطرناک باشد. چرا که یک حملهٔ مرد میانی میتواند شدآمد را به بدون اطَلاعتان به کارسازی دیگر هدایت کند. حملهکننده میتواند گذرواژهها و دیگر دادههای شخصیتان را ضبط کند.</string>
|
||||
<string name="settings_activity_advanced_category_title">پیشرفته</string>
|
||||
<string name="pref_insecure_confirm_title">هشدار!</string>
|
||||
<string name="pref_title_buildtime">زمان ساخت</string>
|
||||
<string name="settings_activity_look_and_feel_category_title">ظاهر</string>
|
||||
<string name="settings_activity_video_playback_category_title">پخش ویدیو</string>
|
||||
<string name="settings_activity_video_list_category_title">فهرست ویدیو</string>
|
||||
<string name="server_book_del_alert_msg">مطمئنید که میخواهید این کارساز را از دفتر نشانی بردارید؟</string>
|
||||
<string name="video_speed_125">۱٫۲۵×</string>
|
||||
<string name="video_speed_075">۰٫۷۵×</string>
|
||||
<string name="title_activity_server_address_book">دفتر نشانی</string>
|
||||
<string name="server_book_no_servers_found">دفترچهٔ کارساز خالیست</string>
|
||||
<string name="authentication_login_failed">ورود شکست خورد!</string>
|
||||
<string name="action_bar_title_address_book">دفتر نشانی</string>
|
||||
<string name="video_meta_button_privacy">محرمانگی</string>
|
||||
<string name="action_bar_title_server_selection">گزینش کارساز</string>
|
||||
<string name="video_speed_20">۲×</string>
|
||||
<string name="video_meta_button_download">بارگیری</string>
|
||||
<string name="video_meta_button_share">همرسانی</string>
|
||||
<string name="video_login_required_for_service">برای استفاده از این خدمت باید وارد شوید</string>
|
||||
<string name="server_selection_filter_hint">سیاههٔ پالایه</string>
|
||||
<string name="purple">ارغوانی</string>
|
||||
<string name="server_selection_set_server">کارساز تنظیم شده به: %s</string>
|
||||
<string name="server_selection_signup_allowed_no">نه</string>
|
||||
<string name="video_speed_15">۱٫۵×</string>
|
||||
<string name="video_speed_05">۰٫۵×</string>
|
||||
<string name="amber">کهربایی</string>
|
||||
<string name="teal">سبز دودی</string>
|
||||
<string name="indigo">لاجوردی</string>
|
||||
<string name="deeppurple">ارغوانی تیره</string>
|
||||
<string name="pink">صورتی</string>
|
||||
<string name="red">قرمز</string>
|
||||
<string name="pref_background_behavior_summary">چگونگی واکنش پخش ویدیو هنگام رفتن به پسزمینه</string>
|
||||
<string name="clear_search_history">پاکسازی تاریخچهٔ جستوجو</string>
|
||||
<string name="af">آفریقایی</string>
|
||||
<string name="title_activity_me">حساب</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">نشانی کارساز</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">نیازمند نشانی اینترنتی معتبر</string>
|
||||
<string name="api_error">چیزی اشتباه پیش رفت. لطفاً بعداً دوباره تلاش کنید!</string>
|
||||
<string name="account_about_joined">پیوسته:</string>
|
||||
<string name="account_about_description">شرح:</string>
|
||||
<string name="account_about_subscribers">مشترکان:</string>
|
||||
<string name="account_about_account">حساب:</string>
|
||||
<string name="account_bottom_menu_about">درباره</string>
|
||||
<string name="account_bottom_menu_channels">کانالها</string>
|
||||
<string name="account_bottom_menu_videos">ویدیوها</string>
|
||||
<string name="menu_video_options_quality" formatted="true">کیفیت (%1$s)</string>
|
||||
<string name="menu_video_options_playback_speed" formatted="true">سرعت پخش (%1$s)</string>
|
||||
<string name="video_meta_button_tags">برچسبها</string>
|
||||
<string name="video_meta_button_language">زبان</string>
|
||||
<string name="video_meta_button_license">پروانه</string>
|
||||
<string name="video_meta_button_category">دسته</string>
|
||||
<string name="video_rating_failed">رتبهبندی شکست خورد</string>
|
||||
<string name="video_download_permission_error">نمیتوان بدون دسترسی نوشتن، ویدیو بارگیری کرد</string>
|
||||
<string name="menu_video_more_blacklist">فهرست سیاه</string>
|
||||
<string name="pref_insecure_confirm_no">نه</string>
|
||||
</resources>
|
|
@ -356,4 +356,9 @@
|
|||
<string name="server_book_add_pick_server_button">Hae</string>
|
||||
<string name="server_book_add_label">Leima</string>
|
||||
<string name="video_list_live_marker">SUORA</string>
|
||||
<string name="pref_title_video_speed">Oletusarvoinen toistonopeus</string>
|
||||
<string name="pref_description_video_speed">Valitse yleinen videon toistonopeus</string>
|
||||
<string name="action_bar_title_address_book">Osoitekirja</string>
|
||||
<string name="video_get_full_description_failed">Koko videon kuvauksen saaminen epäonnistui</string>
|
||||
<string name="video_description_read_more">Lue lisää</string>
|
||||
</resources>
|
|
@ -171,7 +171,7 @@
|
|||
<string name="de">allemand</string>
|
||||
<string name="gsg">langue des signes allemande</string>
|
||||
<string name="gn">guarani</string>
|
||||
<string name="gu">goudjarâtî</string>
|
||||
<string name="gu">goudjarati</string>
|
||||
<string name="ht">haïtien</string>
|
||||
<string name="ha">haoussa</string>
|
||||
<string name="he">hébreu</string>
|
||||
|
@ -361,4 +361,9 @@
|
|||
<string name="pref_title_accept_insecure">Désactiver la vérification des certificats SSL</string>
|
||||
<string name="pref_description_accept_insecure">Ignorer les connexions non sécuritaires. Utiliser ceci seulement si vous connaissez le serveur sur lequel vous vous connectez. Requiert un redémarrage de l\'application.</string>
|
||||
<string name="video_list_live_marker">DIRECT</string>
|
||||
<string name="pref_title_video_speed">Vitesse de lecture par défaut</string>
|
||||
<string name="pref_description_video_speed">Sélectionnez la vitesse globale de lecture vidéo</string>
|
||||
<string name="action_bar_title_address_book">Carnet d\'adresses</string>
|
||||
<string name="video_get_full_description_failed">Impossible de récupérer la description complète de la vidéo</string>
|
||||
<string name="video_description_read_more">Lire la suite</string>
|
||||
</resources>
|
|
@ -297,4 +297,5 @@
|
|||
<string name="api_error">Chaidh rudeigin ceàrr, feuch ris a-rithist an ceann greis!</string>
|
||||
<string name="action_bar_title_server_selection">Tagh frithealaiche</string>
|
||||
<string name="login_current_server_hint">Am frithealaiche làithreach</string>
|
||||
<string name="bottom_nav_title_discover">Foir-shealladh</string>
|
||||
</resources>
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
|
@ -131,7 +131,7 @@
|
|||
<string name="video_speed_075">0.75x</string>
|
||||
<string name="title_activity_server_address_book">Buku Alamat</string>
|
||||
<string name="login_current_server_hint">Server Saat Ini</string>
|
||||
<string name="server_book_list_has_login">Memiliki Login</string>
|
||||
<string name="server_book_list_has_login">Telah Login</string>
|
||||
<string name="server_book_add_save_button">Simpan</string>
|
||||
<string name="server_book_add_add_button">Tambah</string>
|
||||
<string name="server_book_add_password">Kata sandi</string>
|
||||
|
@ -265,7 +265,7 @@
|
|||
<string name="os">Ossetia</string>
|
||||
<string name="om">Oromo</string>
|
||||
<string name="or">Oriya (bahasa makro)</string>
|
||||
<string name="teal">Teal</string>
|
||||
<string name="teal">Sian Hijau</string>
|
||||
<string name="sfs">Bahasa Isyarat Afrika Selatan</string>
|
||||
<string name="so">Somalia</string>
|
||||
<string name="sl">Slovenia</string>
|
||||
|
@ -281,5 +281,66 @@
|
|||
<string name="sc">Sardinia</string>
|
||||
<string name="sg">Sango</string>
|
||||
<string name="sm">Samoa</string>
|
||||
<string name="bluegray">Bluegray</string>
|
||||
<string name="bluegray">Abu-abu Biru</string>
|
||||
<string name="ka">Georgia</string>
|
||||
<string name="de">Jerman</string>
|
||||
<string name="ik">Iñupiat (Alaska)</string>
|
||||
<string name="ja">Jepang</string>
|
||||
<string name="jv">Jawa (Indonesia)</string>
|
||||
<string name="km">Kamboja</string>
|
||||
<string name="lb">Luksembur</string>
|
||||
<string name="mk">Macedonia</string>
|
||||
<string name="mg">Madagaskar</string>
|
||||
<string name="co">Korsika</string>
|
||||
<string name="dz">Bhutan</string>
|
||||
<string name="ff">Senegambia</string>
|
||||
<string name="lg">Ganda</string>
|
||||
<string name="gu">Gujarat (India)</string>
|
||||
<string name="ht">Haiti</string>
|
||||
<string name="ha">Hausa (Afro-Asia)</string>
|
||||
<string name="he">Ibrani</string>
|
||||
<string name="hz">Herero - Afrika Selatan</string>
|
||||
<string name="hi">Hindi (India Utara)</string>
|
||||
<string name="iu">Inuit (Kanada)</string>
|
||||
<string name="ga">Irlandia</string>
|
||||
<string name="it">Italia</string>
|
||||
<string name="ms">Melayu</string>
|
||||
<string name="mn">Mongolia</string>
|
||||
<string name="el">Yunani Modern</string>
|
||||
<string name="pref_description_license">
|
||||
\n<b>Lisensi Publik Umum GNU Affero v3.0</b>
|
||||
\n
|
||||
\nIzin dari lisensi copyleft ini dikondisikan untuk menyediakan kode sumber yang lengkap dari karya berlisensi dan modifikasi, yang mencakup karya yang lebih besar menggunakan karya berlisensi, di bawah lisensi yang sama. Hak cipta dan pemberitahuan lisensi harus dipertahankan. Kontributor memberikan hibah hak paten secara tegas. Ketika versi modifikasi digunakan untuk menyediakan layanan melalui jaringan, kode sumber lengkap dari versi modifikasi harus tersedia.</string>
|
||||
<string name="pref_background_audio">Lanjutkan stream suara di latar belakang</string>
|
||||
<string name="gn">Guarani (Paraguai)</string>
|
||||
<string name="pref_insecure_confirm_message">Anda akan menonaktifkan semua validasi Sertifikasi SSL di Thorium. Menonaktifkannya bisa sangat berbahaya jika server peertube tidak berada di bawah kendali Anda, karena serangan man-in-the-middle dapat mengarahkan lalu lintas ke server lain tanpa sepengetahuan Anda. Seorang penyerang dapat merekam kata sandi dan data pribadi lainnya.</string>
|
||||
<string name="ki">Kikuyu (Kenya)</string>
|
||||
<string name="rw">Rwanda</string>
|
||||
<string name="tlh">StarTrek</string>
|
||||
<string name="lv">Latvia</string>
|
||||
<string name="ko">Korea</string>
|
||||
<string name="lt">Lithuania</string>
|
||||
<string name="kv">Komi (Rusia)</string>
|
||||
<string name="kg">Kongo</string>
|
||||
<string name="avk">Kotava</string>
|
||||
<string name="am">Ethiopia</string>
|
||||
<string name="av">Kaukasian</string>
|
||||
<string name="ho">Papua Nugini</string>
|
||||
<string name="is">Islandia</string>
|
||||
<string name="ig">Igbo (Nigeria)</string>
|
||||
<string name="id">Indonesia</string>
|
||||
<string name="jsl">Bahasa Isyarat Jepang</string>
|
||||
<string name="kl">Greenland</string>
|
||||
<string name="kn">Kannada (barat daya India)</string>
|
||||
<string name="kr">Kanuri (Nigeria)</string>
|
||||
<string name="ks">Kasmir (India)</string>
|
||||
<string name="kk">Kazakhstan</string>
|
||||
<string name="pref_title_video_speed">Kecepatan putar standar</string>
|
||||
<string name="pref_description_video_speed">Pilih secara umum kecepatan putar standar video</string>
|
||||
<string name="pref_description_accept_insecure">Acuhkan koneksi yang tidak aman. Gunakan ini jika Anda tahu peladan yang anda tuju. Muat Ulang Aplikasi.</string>
|
||||
<string name="dv">Maldiva</string>
|
||||
<string name="gl">Galisia (barat laut Spanyol)</string>
|
||||
<string name="hu">Hungaria</string>
|
||||
<string name="ky">Kirgistan</string>
|
||||
<string name="server_selection_nsfw_instance">Saluran NSFW</string>
|
||||
</resources>
|
|
@ -356,4 +356,9 @@
|
|||
<string name="settings_activity_advanced_category_title">Avanzato</string>
|
||||
<string name="server_book_add_save_button">Salva</string>
|
||||
<string name="pref_description_accept_insecure">Ignora le connessioni insicure. Usalo solo se conosci il server a cui ti stai connettendo. Richiede il riavvio dell\'applicazione.</string>
|
||||
<string name="action_bar_title_address_book">Rubrica</string>
|
||||
<string name="pref_title_video_speed">Velocità di riproduzione predefinita</string>
|
||||
<string name="pref_description_video_speed">Seleziona la velocità globale di riproduzione video</string>
|
||||
<string name="video_get_full_description_failed">Impossibile recuperare la descrizione completa del video</string>
|
||||
<string name="video_description_read_more">Leggi di più</string>
|
||||
</resources>
|
|
@ -60,13 +60,13 @@
|
|||
<string name="account_about_subscribers">Abonnenter:</string>
|
||||
<string name="account_about_description">Beskrivelse:</string>
|
||||
<string name="account_about_joined">Tok del:</string>
|
||||
<string name="api_error">Noe gikk galt, prøv igjen senere.</string>
|
||||
<string name="api_error">Noe gikk galt, prøv igjen senere!</string>
|
||||
<string name="permission_rationale">Innvilg kontakttilgang for fullføring av e-postadresser.</string>
|
||||
<string name="bottom_nav_title_trending">Populært</string>
|
||||
<string name="meta_data_views">" visninger"</string>
|
||||
<string name="meta_data_views">" Visninger"</string>
|
||||
<string name="video_row_video_thumbnail">Video-miniatyrbilde</string>
|
||||
<string name="pref_title_show_nsfw">VOKSENT innhold</string>
|
||||
<string name="pref_description_show_nsfw">Vis VOKSENT innhold</string>
|
||||
<string name="pref_title_show_nsfw">innhold upassende på jobb</string>
|
||||
<string name="pref_description_show_nsfw">Vis innhold som er upassende på jobb</string>
|
||||
<string name="pref_language">Språkfilter</string>
|
||||
<string name="pref_description_language">Velg videospråk, istedenfor å vise alle videoer på alle språk.</string>
|
||||
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
|
||||
|
@ -87,7 +87,7 @@
|
|||
<string name="pref_title_dark_mode">Mørkt modus</string>
|
||||
<string name="pref_description_dark_mode">Start programmet for å ikle mørk drakt.</string>
|
||||
<string name="pref_title_app_theme">Programdrakt</string>
|
||||
<string name="pref_description_app_theme">Start programmet på ny for å ikle drakt.</string>
|
||||
<string name="pref_description_app_theme">Start programmet på nytt for å ikle drakt.</string>
|
||||
<string name="indigo">Indigo</string>
|
||||
<string name="cyan">Turkis</string>
|
||||
<string name="lime">Lime</string>
|
||||
|
@ -95,7 +95,7 @@
|
|||
<string name="video_speed_05">0,5×</string>
|
||||
<string name="video_speed_15">1,5×</string>
|
||||
<string name="video_speed_20">2×</string>
|
||||
<string name="pref_description_background_play">Hvis påskrudd vil videoer spilles videre i bakgrunnen.</string>
|
||||
<string name="pref_description_background_play">Video spiller videre i bakgrunnen om dette aktiveres.</string>
|
||||
<string name="video_login_required_for_service">Du må logge inn for å bruke denne tjenesten</string>
|
||||
<string name="ko">Koreansk</string>
|
||||
<string name="action_set_url">Velg tjener</string>
|
||||
|
@ -103,7 +103,7 @@
|
|||
<string name="server_selection_signup_allowed_yes">Ja</string>
|
||||
<string name="server_selection_signup_allowed_no">Nei</string>
|
||||
<string name="server_selection_set_server">Tjener satt til: %s</string>
|
||||
<string name="server_selection_select_a_server">Velg en tjener fra listen, eller skriv den inn direkte</string>
|
||||
<string name="server_selection_select_a_server">Velg en tjener fra listen under, eller skriv inn tjenernavn direkte.</string>
|
||||
<string name="server_selection_peertube_server_url">PeerTube tjenernettadresse</string>
|
||||
<string name="action_bar_title_server_selection">Velg tjener</string>
|
||||
<string name="da">Dansk</string>
|
||||
|
@ -132,14 +132,14 @@
|
|||
<string name="pref_background_behavior">Oppsett av bakgrunnsavspilling</string>
|
||||
<string name="pref_background_float">Fortsett video|avspilling i flytende vindu</string>
|
||||
<string name="pref_description_back_pause">Opphold i bakgrunnsavspilling når \"Tilbake\" trykkes under videoavspilling.</string>
|
||||
<string name="pref_title_back_pause">Sett på pause med \"Tilbake\"-knapp</string>
|
||||
<string name="pref_title_back_pause">Pause når tilbake-knappen brukes</string>
|
||||
<string name="pref_background_stop">Stopp all avspilling</string>
|
||||
<string name="pref_background_audio">Fortsett som lydstrøm i bakgrunnen</string>
|
||||
<string name="pref_description_language_app">Velg språk for programgrensesnittet. Start programmet på ny for å utføre endringene.</string>
|
||||
<string name="pref_language_app">Programspråk</string>
|
||||
<string name="server_book_add_server_url">Tjener-nettadresse</string>
|
||||
<string name="server_book_add_pick_server_button">Søk</string>
|
||||
<string name="server_book_add_username">Username</string>
|
||||
<string name="server_book_add_username">Brukernavn</string>
|
||||
<string name="server_book_add_add_button">Legg til</string>
|
||||
<string name="server_book_add_password">Passord</string>
|
||||
<string name="title_activity_server_address_book">Adressebok</string>
|
||||
|
@ -164,7 +164,7 @@
|
|||
<string name="me_logout_button">Logg ut</string>
|
||||
<string name="server_book_valid_url_is_required">Gyldig nettadresse kreves</string>
|
||||
<string name="server_book_label_is_required">Tjeneretikett kreves</string>
|
||||
<string name="authentication_login_failed">Kunne ikke logge inn</string>
|
||||
<string name="authentication_login_failed">Kunne ikke logge inn!</string>
|
||||
<string name="authentication_login_success">Innlogget</string>
|
||||
<string name="hello_blank_fragment">Hei blanke fragment</string>
|
||||
<string name="network_error">Tilknytningsfeil, sjekk tilkoblingen din</string>
|
||||
|
@ -185,14 +185,25 @@
|
|||
<string name="gu">gujarati</string>
|
||||
<string name="bn">bengalsk</string>
|
||||
<string name="hy">armensk</string>
|
||||
<string name="server_selection_nsfw_instance">Arbeidsutrygg insans</string>
|
||||
<string name="server_selection_nsfw_instance">jobb-upassende instans</string>
|
||||
<string name="pref_insecure_confirm_yes">Ja</string>
|
||||
<string name="pref_insecure_confirm_no">Nei</string>
|
||||
<string name="pref_insecure_confirm_title">Advarsel!</string>
|
||||
<string name="settings_activity_advanced_category_title">Avansert</string>
|
||||
<string name="server_book_add_save_button">Lagre</string>
|
||||
<string name="pref_insecure_confirm_message">Du er i ferd med å skru av all SSL-sertifisering i Thorium. Å skru av dette kan være veldig farlig hvis Peertube ikke er under din kontroll, fordi mellommanns-angrep kan sende trafikk til en annen tjener uten at du vet det. En angriper kan da se passordene når de blir brukt, og annen personlig data.</string>
|
||||
<string name="pref_insecure_confirm_message">Du er i ferd med å skru av all kontroll av SSL-sertifikater i Thorium. Å skru av dette kan være veldig farlig hvis Peertube-tjeneren ikke er under din kontroll, fordi mellommanns-angrep kan styre trafikk til en annen tjener uten at du vet det. En angriper kan da registrere passord, og annen personlig data.</string>
|
||||
<string name="video_list_live_marker">Sanntid</string>
|
||||
<string name="pref_title_accept_insecure">Skru av SSL-sertifikatssjekk</string>
|
||||
<string name="pref_description_accept_insecure">Ignorer usikre tilkoblinger. Kun bruk dette hvis du vet hvilken tjener du kobler til. Krever programomstart.</string>
|
||||
<string name="pref_description_accept_insecure">Ignorer usikre tilkoblinger. Bruk kun dette hvis du vet hvilken tjener du kobler til. Krever programomstart.</string>
|
||||
<string name="es">Spansk</string>
|
||||
<string name="id">Indonesisk</string>
|
||||
<string name="action_bar_title_address_book">Adressebok</string>
|
||||
<string name="pref_title_video_speed">Forvalgt avspillingshastighet</string>
|
||||
<string name="pref_description_video_speed">Velg videoavspillingshastighet for hele systemet</string>
|
||||
<string name="ab">Abkhasisk</string>
|
||||
<string name="video_description_read_more">Les mer</string>
|
||||
<string name="video_get_full_description_failed">Kunne ikke hente komplett videobeskrivelse</string>
|
||||
<string name="so">Somalisk</string>
|
||||
<string name="sk">Slovakisk</string>
|
||||
<string name="sfs">Sørafrikansk tegnspråk</string>
|
||||
</resources>
|
|
@ -318,4 +318,9 @@
|
|||
<string name="pref_background_behavior">Ustawienia odtwarzania w tle</string>
|
||||
<string name="pref_background_float">Kontynuuj odtwarzanie w ruchomym oknie</string>
|
||||
<string name="pref_language_app">Język Aplikacji</string>
|
||||
<string name="pref_title_video_speed">Domyślna prędkość odtwarzania</string>
|
||||
<string name="pref_description_video_speed">Wybierz globalną szybkość odtwarzania wideo</string>
|
||||
<string name="pref_title_back_pause">Wstrzymanie po naciśnięciu przycisku wstecz</string>
|
||||
<string name="settings_api_error_float">Android nie wspiera odtwarzania w ruchomym oknie</string>
|
||||
<string name="pref_description_language_app">Wybierz język interfejsu aplikacji. Wymaga restartu, by zmiany nabrały efektu.</string>
|
||||
</resources>
|
|
@ -356,4 +356,9 @@
|
|||
<string name="pref_title_accept_insecure">Desativar check do certificado SSL</string>
|
||||
<string name="pref_description_accept_insecure">Ignorar conexões não seguras. Use isto apenas se você conhece o servidor ao qual está se conectando. Requer o reinício do aplicativo.</string>
|
||||
<string name="video_list_live_marker">AO VIVO</string>
|
||||
<string name="pref_title_video_speed">Velocidade de reprodução padrão</string>
|
||||
<string name="pref_description_video_speed">Selecione a velocidade global de reprodução de vídeo</string>
|
||||
<string name="action_bar_title_address_book">Lista de endereços</string>
|
||||
<string name="video_get_full_description_failed">Não foi possível obter a descrição completa do vídeo</string>
|
||||
<string name="video_description_read_more">Leia mais</string>
|
||||
</resources>
|
|
@ -356,4 +356,9 @@
|
|||
<string name="server_book_add_save_button">Guardar</string>
|
||||
<string name="pref_title_accept_insecure">Desativar a verificação do certificado SSL</string>
|
||||
<string name="pref_description_accept_insecure">Ignorar conexões inseguras. Use isto apenas se souber a qual servidor está a conectar-se. Requer reinicialização da aplicação.</string>
|
||||
<string name="pref_title_video_speed">Velocidade de reprodução predefinida</string>
|
||||
<string name="pref_description_video_speed">Selecione a velocidade de reprodução de vídeo global</string>
|
||||
<string name="action_bar_title_address_book">Lista de contactos</string>
|
||||
<string name="video_get_full_description_failed">Não foi possível obter a descrição completa do vídeo</string>
|
||||
<string name="video_description_read_more">Leia mais</string>
|
||||
</resources>
|
|
@ -362,4 +362,9 @@
|
|||
<string name="pref_insecure_confirm_message">"Вы собираетесь отключить валидацию всех SSL сертификатов в Thorium. Это может быть очень опасно если peertube сервер вами не контролируется, потому что \"атака посредника\" может направить трафик на другой сервер. Злоумышленник может записывать пароли и другие личные данные."</string>
|
||||
<string name="server_book_add_save_button">Сохранить</string>
|
||||
<string name="video_list_live_marker">В ЭФИРЕ</string>
|
||||
<string name="action_bar_title_address_book">Адресная книга</string>
|
||||
<string name="pref_title_video_speed">Скорость воспроизведения по умолчанию</string>
|
||||
<string name="pref_description_video_speed">Выберите глобальную скорость воспроизведения видео</string>
|
||||
<string name="video_description_read_more">Подробнее</string>
|
||||
<string name="video_get_full_description_failed">Не удалось получить полное описание видео</string>
|
||||
</resources>
|
|
@ -1,2 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="title_activity_login">Intra</string>
|
||||
<string name="prompt_password">Crae</string>
|
||||
<string name="action_bar_title_search">Chirca</string>
|
||||
<string name="action_bar_title_account">Contu</string>
|
||||
<string name="bottom_nav_title_account">Contu</string>
|
||||
<string name="action_bar_title_settings">impostatziones</string>
|
||||
<string name="action_sign_in">Intra</string>
|
||||
<string name="action_sign_in_short">Intra</string>
|
||||
<string name="bottom_nav_title_recent">Reghente</string>
|
||||
<string name="bottom_nav_title_local">Locale</string>
|
||||
<string name="descr_overflow_button">Àteru</string>
|
||||
<string name="menu_share">Cumpartzi</string>
|
||||
<string name="pref_title_dark_mode">Modalidade iscura</string>
|
||||
<string name="pref_title_app_theme">Tema de s\'aplicatzione</string>
|
||||
<string name="pref_title_torrent_player">Riprodusidore de vìdeu Torrent</string>
|
||||
<string name="pref_title_version">Versione</string>
|
||||
<string name="pref_title_show_nsfw">Cuntenutu pro adultos</string>
|
||||
<string name="pref_description_show_nsfw">Ammustra cuntenutu pro adultos</string>
|
||||
<string name="pref_title_peertube_server">Serbidore PeerTube</string>
|
||||
<string name="invalid_url">URL non bàlidu.</string>
|
||||
<string name="pref_title_background_play">Riprodutzione in isfundu</string>
|
||||
<string name="account_about_account">Contu:</string>
|
||||
<string name="account_about_subscribers">Sutiscritos:</string>
|
||||
<string name="server_book_add_username">Nòmine usuàriu</string>
|
||||
<string name="server_book_add_password">Crae</string>
|
||||
<string name="server_book_add_add_button">Annanghe</string>
|
||||
<string name="settings_activity_about_category_title">A pitzu de</string>
|
||||
<string name="pref_insecure_confirm_title">Atentzione!</string>
|
||||
<string name="title_activity_search">Chirca</string>
|
||||
<string name="title_activity_settings">Impostatziones</string>
|
||||
<string name="search_hint">Chirca PeerTube</string>
|
||||
<string name="prompt_server">Serbidore</string>
|
||||
<string name="no_data_available">Perunu resurtadu</string>
|
||||
<string name="pref_language">Filtru de limba</string>
|
||||
<string name="pref_title_license">Litzèntzia</string>
|
||||
<string name="pref_insecure_confirm_yes">Eja</string>
|
||||
<string name="server_book_add_label">Eticheta</string>
|
||||
<string name="account_about_description">Descritzione:</string>
|
||||
<string name="account_bottom_menu_about">A pitzu de</string>
|
||||
<string name="server_book_add_pick_server_button">Chirca</string>
|
||||
<string name="server_book_add_save_button">Sarva</string>
|
||||
<string name="video_speed_075">0.75x</string>
|
||||
<string name="video_speed_125">1.25x</string>
|
||||
<string name="title_activity_settings2">ImpostatzionesAtividade2</string>
|
||||
<string name="title_activity_me">Contu</string>
|
||||
<string name="pref_insecure_confirm_no">Nono</string>
|
||||
<string name="video_list_live_marker">IN DIRETA</string>
|
||||
</resources>
|
|
@ -350,4 +350,11 @@
|
|||
<string name="prompt_server">Shërbyes</string>
|
||||
<string name="title_activity_login">Hyni</string>
|
||||
<string name="title_activity_settings">Rregullime</string>
|
||||
<string name="action_bar_title_address_book">Libër Adresash</string>
|
||||
<string name="video_get_full_description_failed">S’u pru dot përshkrimi i plotë i videos</string>
|
||||
<string name="video_description_read_more">Lexoni Më Tepër</string>
|
||||
<string name="pref_description_video_speed">Përzgjidhni Shpejtësi globale për Luajtje Videosh</string>
|
||||
<string name="pref_title_video_speed">Shpejtësi Parazgjedhje Luajtjeje</string>
|
||||
<string name="rm">Romansh</string>
|
||||
<string name="pref_title_buildtime">Kohë Montimi</string>
|
||||
</resources>
|
|
@ -89,7 +89,7 @@
|
|||
<string name="da">Danimarkaca</string>
|
||||
<string name="dsl">Danimarka İşaret Dili</string>
|
||||
<string name="dv">Maldivce</string>
|
||||
<string name="nl">Flemenkçe</string>
|
||||
<string name="nl">Felemenkçe</string>
|
||||
<string name="dz">Dzongka</string>
|
||||
<string name="en">İngilizce</string>
|
||||
<string name="eo">Esperanto</string>
|
||||
|
@ -371,4 +371,9 @@
|
|||
<string name="pref_description_accept_insecure">Güvenli olmayan bağlantıları yok sayın. Bunu yalnızca bağlandığınız sunucuyu biliyorsanız kullanın. Uygulamanın yeniden başlatılmasını gerektirir.</string>
|
||||
<string name="server_book_add_save_button">Kaydet</string>
|
||||
<string name="video_list_live_marker">CANLI</string>
|
||||
<string name="pref_title_video_speed">Öntanımlı Oynatma Hızı</string>
|
||||
<string name="pref_description_video_speed">Genel Video Oynatma Hızını Seçin</string>
|
||||
<string name="action_bar_title_address_book">Adres Defteri</string>
|
||||
<string name="video_get_full_description_failed">Tam video açıklaması alınamadı</string>
|
||||
<string name="video_description_read_more">Daha Fazla Oku</string>
|
||||
</resources>
|
|
@ -12,7 +12,7 @@
|
|||
<string name="bottom_nav_title_discover">Огляд</string>
|
||||
<string name="menu_video_options_quality_automated">Автоматизовано</string>
|
||||
<string name="server_selection_video_totals" formatted="false">Відео: %s, Локальні відео: %s</string>
|
||||
<string name="server_selection_nsfw_instance">Екземпляр NSFW</string>
|
||||
<string name="server_selection_nsfw_instance">Сервер NSFW</string>
|
||||
<string name="settings_activity_video_playback_category_title">Відтворення відео</string>
|
||||
<string name="settings_activity_video_list_category_title">Список відео</string>
|
||||
<string name="title_activity_settings2">SettingsActivity2</string>
|
||||
|
@ -39,16 +39,16 @@
|
|||
<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_behavior">Налаштування відтворення у тлі</string>
|
||||
<string name="pref_background_behavior">Налаштування відтворення у фоні</string>
|
||||
<string name="pref_background_float">Продовжити відтворення відео у плаваючому вікні</string>
|
||||
<string name="pref_background_stop">Зупинити все відтворення</string>
|
||||
<string name="pref_background_audio">Продовжити фоновим аудіопотоком</string>
|
||||
<string name="pref_description_language_app">Виберіть мову інтерфейсу застосунку. Перезапустіть застосунок, щоб зміни набули чинності.</string>
|
||||
<string name="pref_language_app">Мова застосунку</string>
|
||||
<string name="pref_description_back_pause">Зупиняти відтворення в тлі натисканням кнопки назад під час відтворення відео.</string>
|
||||
<string name="pref_description_back_pause">Зупиняти відтворення у фоні натисканням кнопки назад під час відтворення відео.</string>
|
||||
<string name="pref_title_back_pause">Зупиняти кнопкою назад</string>
|
||||
<string name="pref_description_background_play">Якщо ввімкнено, продовжує відтворювати відео у фоновому режимі.</string>
|
||||
<string name="pref_title_background_play">Відтворення в тлі</string>
|
||||
<string name="pref_title_background_play">Відтворення у фоні</string>
|
||||
<string name="pref_title_peertube_server">Сервер PeerTube</string>
|
||||
<string name="pref_description_language">Виберіть мову відео, замість перегляду всіх відео всіма мовами.</string>
|
||||
<string name="pref_language">Фільтр мов</string>
|
||||
|
@ -115,7 +115,7 @@
|
|||
<string name="pink">Рожевий</string>
|
||||
<string name="red">Червоний</string>
|
||||
<string name="zu">зулуська</string>
|
||||
<string name="za">чуанг</string>
|
||||
<string name="za">чжуанська</string>
|
||||
<string name="yo">йоруба</string>
|
||||
<string name="yi">ідиш</string>
|
||||
<string name="xh">коса</string>
|
||||
|
@ -312,7 +312,7 @@
|
|||
<string name="pref_title_torrent_player">Торрент-програвач відео</string>
|
||||
<string name="pref_description_app_theme">Перезапустіть застосунок, щоб темний режим набув чинності.</string>
|
||||
<string name="pref_title_app_theme">Тема застосунку</string>
|
||||
<string name="pref_description_dark_mode">Перезапустіть програму, щоб темний режим набув чинності.</string>
|
||||
<string name="pref_description_dark_mode">Перезапустіть застосунок, щоб темний режим набув чинності.</string>
|
||||
<string name="pref_title_dark_mode">Темний режим</string>
|
||||
<string name="invalid_url">Недійсна URL-адреса.</string>
|
||||
<string name="menu_share">Поділитися</string>
|
||||
|
@ -325,9 +325,9 @@
|
|||
<string name="video_row_video_thumbnail">Ескіз відео</string>
|
||||
<string name="meta_data_views">" переглядів"</string>
|
||||
<string name="bottom_nav_title_subscriptions">Підписки</string>
|
||||
<string name="bottom_nav_title_local">Локальне</string>
|
||||
<string name="bottom_nav_title_recent">Нещодавнє</string>
|
||||
<string name="bottom_nav_title_trending">Популярне</string>
|
||||
<string name="bottom_nav_title_local">Локальні</string>
|
||||
<string name="bottom_nav_title_recent">Нещодавні</string>
|
||||
<string name="bottom_nav_title_trending">Популярні</string>
|
||||
<string name="action_bar_title_search">Шукати</string>
|
||||
<string name="permission_rationale">Надайте доступ до контактів для заповнення електронної пошти.</string>
|
||||
<string name="prompt_password">Пароль</string>
|
||||
|
@ -343,12 +343,12 @@
|
|||
<string name="title_activity_me">Обліковий запис</string>
|
||||
<string name="action_bar_title_account">Обліковий запис</string>
|
||||
<string name="action_bar_title_logout">Вийти</string>
|
||||
<string name="action_bar_title_settings">Параметри</string>
|
||||
<string name="action_bar_title_settings">Налаштування</string>
|
||||
<string name="prompt_server">Сервер</string>
|
||||
<string name="title_activity_login">Увійти</string>
|
||||
<string name="title_activity_settings">Параметри</string>
|
||||
<string name="title_activity_settings">Налаштування</string>
|
||||
<string name="pref_title_accept_insecure">Вимкнути перевірку SSL сертифіката</string>
|
||||
<string name="pref_description_accept_insecure">Ігнорувати незахищені з\'єднання. Використовуйте це лише якщо знаєте сервер до якого підключаєтесь. Перезапустіть застосунок, щоб зміни набули чинності.</string>
|
||||
<string name="pref_description_accept_insecure">Ігнорувати незахищені з\'єднання. Використовуйте лише якщо знаєте сервер до якого підʼєднуєтесь. Перезапустіть застосунок, щоб зміни набули чинності.</string>
|
||||
<string name="pref_insecure_confirm_yes">Так</string>
|
||||
<string name="pref_insecure_confirm_no">Ні</string>
|
||||
<string name="pref_insecure_confirm_title">Увага!</string>
|
||||
|
@ -356,4 +356,9 @@
|
|||
<string name="settings_activity_advanced_category_title">Додатково</string>
|
||||
<string name="server_book_add_save_button">Зберегти</string>
|
||||
<string name="video_list_live_marker">НАЖИВО</string>
|
||||
<string name="pref_title_video_speed">Типова швидкість відтворення</string>
|
||||
<string name="pref_description_video_speed">Вибрати загальну швидкість відтворення відео</string>
|
||||
<string name="action_bar_title_address_book">Адресна книга</string>
|
||||
<string name="video_get_full_description_failed">Не вдалося отримати повний опис відео</string>
|
||||
<string name="video_description_read_more">Детальніше</string>
|
||||
</resources>
|
|
@ -127,7 +127,7 @@
|
|||
<string name="server_selection_select_a_server">从下面的列表选择一个服务器或者手动输入。</string>
|
||||
<string name="server_selection_peertube_server_url">PeerTube 服务器 URL</string>
|
||||
<string name="action_bar_title_server_selection">选择服务器</string>
|
||||
<string name="login_current_server_hint">现在的服务器</string>
|
||||
<string name="login_current_server_hint">当前的服务器</string>
|
||||
<string name="video_speed_075">0.75倍速</string>
|
||||
<string name="video_speed_125">1.25倍速</string>
|
||||
<string name="pt">葡萄牙语</string>
|
||||
|
@ -308,4 +308,33 @@
|
|||
<string name="ro">罗马尼亚语</string>
|
||||
<string name="qu">克丘亚语</string>
|
||||
<string name="ps">普什图语</string>
|
||||
<string name="me_help_and_feedback_button">帮助与反馈</string>
|
||||
<string name="authentication_login_failed">登录失败!</string>
|
||||
<string name="server_selection_filter_hint">过滤列表</string>
|
||||
<string name="pref_title_video_speed">默认播放速度</string>
|
||||
<string name="settings_activity_about_category_title">关于</string>
|
||||
<string name="server_book_del_alert_msg">您确定您想从地址薄中移除该服务器吗?</string>
|
||||
<string name="pref_description_video_speed">选择全局视频播放速度</string>
|
||||
<string name="server_book_add_label">标签</string>
|
||||
<string name="server_book_add_server_url">服务器 URL</string>
|
||||
<string name="server_book_add_pick_server_button">搜索</string>
|
||||
<string name="server_book_add_username">用户名</string>
|
||||
<string name="server_book_add_password">密码</string>
|
||||
<string name="server_book_add_add_button">添加</string>
|
||||
<string name="server_book_add_save_button">保存</string>
|
||||
<string name="server_book_list_has_login">已登录</string>
|
||||
<string name="title_activity_server_address_book">地址薄</string>
|
||||
<string name="server_book_label_is_required">服务器标签必填</string>
|
||||
<string name="network_error">网络访问错误,请检查您的网络连接</string>
|
||||
<string name="authentication_login_success">已登录</string>
|
||||
<string name="me_logout_button">退出登录</string>
|
||||
<string name="server_book_del_alert_title">移除服务器</string>
|
||||
<string name="title_activity_select_server">搜索服务器</string>
|
||||
<string name="title_activity_me">账号</string>
|
||||
<string name="settings_activity_video_playback_category_title">视频播放</string>
|
||||
<string name="server_book_valid_url_is_required">有效的 URL 必填</string>
|
||||
<string name="settings_activity_video_list_category_title">视频列表</string>
|
||||
<string name="pref_insecure_confirm_yes">是</string>
|
||||
<string name="pref_insecure_confirm_no">否</string>
|
||||
<string name="video_list_live_marker">直播</string>
|
||||
</resources>
|
|
@ -356,4 +356,9 @@
|
|||
<string name="pref_description_accept_insecure">忽略不安全的連線。僅在您了解您要連線的伺服器時才使用此選項。需要重新啟動應用程式。</string>
|
||||
<string name="server_book_add_save_button">儲存</string>
|
||||
<string name="video_list_live_marker">直播</string>
|
||||
<string name="pref_title_video_speed">預設播放速度</string>
|
||||
<string name="pref_description_video_speed">選取全域影片播放速度</string>
|
||||
<string name="action_bar_title_address_book">通訊錄</string>
|
||||
<string name="video_get_full_description_failed">無法擷取完整影片描述</string>
|
||||
<string name="video_description_read_more">閱讀更多資訊</string>
|
||||
</resources>
|
|
@ -58,12 +58,12 @@
|
|||
<string name="video_download_icon" translatable="false">{faw-download}</string>
|
||||
<string name="video_save_icon" translatable="false">{faw-save}</string>
|
||||
|
||||
<string name="meta_data_owner_seperator" translatable="false">\@</string>
|
||||
<string name="meta_data_seperator" translatable="false">\u0020-\u0020</string>
|
||||
|
||||
<string name="title_activity_video_play" translatable="false">VideoPlayActivity</string>
|
||||
|
||||
<string name="playback_channel_name" translatable="false">PeerTube</string>
|
||||
<string name="playback_channel_description" translatable="false">playback_channel</string>
|
||||
|
||||
<string name="peertube_instance_search_default_description" translatable="false">PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.</string>
|
||||
|
||||
|
|
|
@ -364,6 +364,28 @@
|
|||
<string name="pref_insecure_confirm_yes">Yes</string>
|
||||
<string name="pref_insecure_confirm_message">You are about the disable all SSL Certification validation in Thorium. Disabling this can be very dangerous if the peertube server is not under your control, because a man-in-the-middle attack could direct traffic to another server without your knowledge. An attacker could record passwords and other personal data.</string>
|
||||
<string name="video_list_live_marker">LIVE</string>
|
||||
<string name="video_get_full_description_failed">Getting full video description failed</string>
|
||||
<string name="video_get_full_description_failed">Could not fetch the full video description</string>
|
||||
<string name="video_description_read_more">Read More</string>
|
||||
<string name="player_time_seperator">/</string>
|
||||
<string name="video_meta_show_description">Show Description</string>
|
||||
<string name="video_meta_title_description">Description</string>
|
||||
<string name="video_add_to_playlist">Save</string>
|
||||
<string name="video_block">Block</string>
|
||||
<string name="video_flag">Flag</string>
|
||||
<string name="video_feature_not_yet_implemented">This feature has not yet been implemented. Coming soon!</string>
|
||||
<string name="subscribe">Subscribe</string>
|
||||
<string name="unsubscribe">Unsubscribe</string>
|
||||
<string name="video_comments_title">Comments</string>
|
||||
<plurals name="video_channel_subscribers">
|
||||
<item quantity="one">%1$d subscriber</item>
|
||||
<item quantity="other">%1$d subscribers</item>
|
||||
</plurals>
|
||||
<string name="video_by_line">By %1$s</string>
|
||||
<string name="video_owner_fqdn_line">%1$s@%2$s</string>
|
||||
<string name="video_sub_del_alert_title">Unsubscribe</string>
|
||||
<string name="video_sub_del_alert_msg">Are you sure you would like to unsubscribe?</string>
|
||||
<string name="saved_to_playlist">Saved to playlist</string>
|
||||
<string name="remove_video">Remove Video</string>
|
||||
<string name="remove_video_warning_message">Are you sure you want to remove this video from playlist?</string>
|
||||
<string name="playlist">Playlist</string>
|
||||
</resources>
|
|
@ -76,12 +76,12 @@
|
|||
app:title="@string/pref_background_behavior"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:defaultValue="false"
|
||||
app:key="@string/pref_torrent_player_key"
|
||||
app:summary="@string/pref_description_torrent_player"
|
||||
app:title="@string/pref_title_torrent_player"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<!-- <SwitchPreference-->
|
||||
<!-- app:defaultValue="false"-->
|
||||
<!-- app:key="@string/pref_torrent_player_key"-->
|
||||
<!-- app:summary="@string/pref_description_torrent_player"-->
|
||||
<!-- app:title="@string/pref_title_torrent_player"-->
|
||||
<!-- app:iconSpaceReserved="false"/>-->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
buildscript {
|
||||
|
||||
ext.kotlin_version = '1.5.31'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
|
@ -21,7 +21,6 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://oss.sonatype.org/content/repositories/snapshots'
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Reset
|
||||
Color_Off='\033[0m' # Text Reset
|
||||
|
||||
# Regular Colors
|
||||
Black='\033[0;30m' # Black
|
||||
Red='\033[0;31m' # Red
|
||||
Green='\033[0;32m' # Green
|
||||
Yellow='\033[0;33m' # Yellow
|
||||
Blue='\033[0;34m' # Blue
|
||||
Purple='\033[0;35m' # Purple
|
||||
Cyan='\033[0;36m' # Cyan
|
||||
White='\033[0;37m' # White
|
||||
|
||||
exitcode=0
|
||||
|
||||
supportedlangs=(af ar am hy-AM az-AZ eu-ES be bn-BD bg my-MM ca zh-HK zh-CN zh-TW hr cs-CZ da-DK nl-NL en-AU en-CA en-IN en-SG en-GB en-US et fil fi-FI fr-FR fr-CA gl-ES ka-GE de-DE el-GR iw-IL hi-IN hu-HU is-IS id it-IT ja-JP kn-IN km-KH ko-KR ky-KG lo-LA lv lt mk-MK ms ml-IN mr-IN mn-MN ne-NP no-NO fa pl-PL pt-BR pt-PT ro rm ru-RU sr si-LK sk sl es-419 es-ES es-US sq sw sv-SE ta-IN te-IN th tr-TR uk vi zu)
|
||||
|
||||
readarray -t dirs < <(find fastlane/metadata/android -mindepth 1 -maxdepth 1 -type d -printf '%P\n')
|
||||
|
||||
echo -e "${Green}Checking for Valid fastlane files...${Color_Off}"
|
||||
|
||||
for target in "${supportedlangs[@]}"; do
|
||||
for i in "${!dirs[@]}"; do
|
||||
if [[ ${dirs[i]} = $target ]]; then
|
||||
unset 'dirs[i]'
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [[ ${#dirs[@]} -gt 0 ]]; then
|
||||
exitcode=1
|
||||
echo -e "${Red}Invalid Lang Play Store Listing found: ${#dirs[@]}${Color_Off}"
|
||||
echo -e "${Red}Invalid Lang codes:${dirs[@]}${Color_Off}"
|
||||
else
|
||||
echo -e "${Green}All found lang codes are valid${Color_Off}"
|
||||
fi
|
||||
|
||||
# check we have required files
|
||||
|
||||
requiredfiles=(title.txt full_description.txt short_description.txt)
|
||||
|
||||
for d in fastlane/metadata/android/* ; do
|
||||
[ -L "${d%/}" ] && continue
|
||||
for rfile in "${requiredfiles[@]}"; do
|
||||
if test ! -f "$d/$rfile"; then
|
||||
echo -e "${Red}$d/$rfile missing.${Color_Off}"
|
||||
exitcode=1
|
||||
fi
|
||||
done
|
||||
# check title is under 30 characters
|
||||
if test -f "$d/title.txt"; then
|
||||
if [[ $(wc -m < "$d/title.txt") -gt 30 ]]; then
|
||||
echo -e "${Red}$d/title.txt title too long.${Color_Off}"
|
||||
exitcode=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# if test -f "$d/video.txt"; then
|
||||
# fcontents=$(cat "$d/video.txt")
|
||||
# if [[ "$fcontents" != "https://www.youtube.com/watch?v=lVJs26gE2Ek" ]]; then
|
||||
# exitcode=1
|
||||
# echo -e "${Blue}$fcontents${Color_Off} --> $d/video.txt - not correct video URL"
|
||||
# fi
|
||||
# fi
|
||||
done
|
||||
|
||||
exit $exitcode
|
|
@ -1 +1 @@
|
|||
الثوريوم عميل غير رسمي لشركة P
|
||||
Thorium a PeerTube client
|
|
@ -1 +0,0 @@
|
|||
https://www.youtube.com/watch?v=lVJs26gE2Ek
|
|
@ -1 +1 @@
|
|||
থোরিয়াম একটি অনানুষ্ঠানিক পিয়ারটিউব ক্লায়েন্ট
|
||||
Thorium a PeerTube client
|
|
@ -1 +0,0 @@
|
|||
https://www.youtube.com/watch?v=lVJs26gE2Ek
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue