mirror of
https://framagit.org/tom79/fedilab-tube
synced 2025-06-05 21:09:11 +02:00
Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
7bc018314c | |||
a04d27dd4c | |||
5533929cc5 | |||
8ae8467791 | |||
c2225bca37 | |||
d4270a3ef9 | |||
cd68de6274 | |||
c5866b1acf | |||
bd1794a10e | |||
f120d8fad5 | |||
70e8133350 | |||
2cdabbcb70 | |||
d5ad9b181b | |||
540fb3e2dc | |||
edbe65593b | |||
d5f394dfea | |||
d5a5fdf52e | |||
a7f9256947 | |||
d769729901 | |||
b2026c8784 | |||
b29de141ef | |||
6d4772da75 | |||
2344fe0942 | |||
7c309b68b8 | |||
c8c5e56a17 | |||
cef227ba42 | |||
02296b038a | |||
b76a4cfcf5 | |||
f168f101bc | |||
2e8a86fe20 | |||
fe0d2fe726 | |||
9b322cc922 | |||
346656e53d | |||
6e6187175a | |||
5e832fa046 | |||
34007d4507 | |||
fd400f025e | |||
f3f474ee13 | |||
d971032d52 | |||
961c77103e | |||
b22b21c47a | |||
6d70bd758a | |||
087ac92f15 | |||
99fe789f30 | |||
10892f92f1 | |||
0a919c85ab | |||
761abc013f |
133
.gitlab-ci.yml
133
.gitlab-ci.yml
@ -1,133 +0,0 @@
|
||||
# This file is a template, and might need editing before it works on your project.
|
||||
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
|
||||
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.
|
||||
|
||||
image: openjdk:8-jdk
|
||||
|
||||
variables:
|
||||
|
||||
# ANDROID_COMPILE_SDK is the version of Android you're compiling with.
|
||||
# It should match compileSdkVersion.
|
||||
ANDROID_COMPILE_SDK: "30"
|
||||
|
||||
# ANDROID_BUILD_TOOLS is the version of the Android build tools you are using.
|
||||
# It should match buildToolsVersion.
|
||||
ANDROID_BUILD_TOOLS: "30.0.2"
|
||||
|
||||
# It's what version of the command line tools we're going to download from the official site.
|
||||
# Official Site-> https://developer.android.com/studio/index.html
|
||||
# There, look down below at the cli tools only, sdk tools package is of format:
|
||||
# commandlinetools-os_type-ANDROID_SDK_TOOLS_latest.zip
|
||||
# when the script was last modified for latest compileSdkVersion, it was which is written down below
|
||||
ANDROID_SDK_TOOLS: "6609375"
|
||||
|
||||
# Packages installation before running script
|
||||
before_script:
|
||||
- apt-get --quiet update --yes
|
||||
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
|
||||
|
||||
# Setup path as android_home for moving/exporting the downloaded sdk into it
|
||||
- export ANDROID_HOME="${PWD}/android-home"
|
||||
# Create a new directory at specified location
|
||||
- install -d $ANDROID_HOME
|
||||
# Here we are installing androidSDK tools from official source,
|
||||
# (the key thing here is the url from where you are downloading these sdk tool for command line, so please do note this url pattern there and here as well)
|
||||
# after that unzipping those tools and
|
||||
# then running a series of SDK manager commands to install necessary android SDK packages that'll allow the app to build
|
||||
- wget --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
|
||||
# move to the archive at ANDROID_HOME
|
||||
- pushd $ANDROID_HOME
|
||||
- unzip -d cmdline-tools cmdline-tools.zip
|
||||
- popd
|
||||
- export PATH=$PATH:${ANDROID_HOME}/cmdline-tools/tools/bin/
|
||||
|
||||
# Nothing fancy here, just checking sdkManager version
|
||||
- sdkmanager --version
|
||||
|
||||
# use yes to accept all licenses
|
||||
- yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses || true
|
||||
- sdkmanager --sdk_root=${ANDROID_HOME} "platforms;android-${ANDROID_COMPILE_SDK}"
|
||||
- sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools"
|
||||
- sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;${ANDROID_BUILD_TOOLS}"
|
||||
|
||||
# Not necessary, but just for surity
|
||||
- chmod +x ./gradle
|
||||
|
||||
stages:
|
||||
- build
|
||||
- build-and-test
|
||||
- tag
|
||||
|
||||
.no-upload: &no-upload
|
||||
stage: build-and-test
|
||||
retry: 2
|
||||
|
||||
# Make Project
|
||||
assembleDebug:
|
||||
<<: *no-upload
|
||||
cache:
|
||||
key: "${CI_COMMIT_TAG}"
|
||||
paths:
|
||||
- app/build/outputs/apk/fdroid_peertube_apps_educ/debug/app-fdroid_peertube_apps_educ-debug.apk
|
||||
- app/build/outputs/apk/fdroid_full/debug/app-fdroid_full-debug.apk
|
||||
policy: push
|
||||
script:
|
||||
- ./gradlew assembleDebug
|
||||
|
||||
# Basic android and gradle stuff
|
||||
# Check linting
|
||||
lintfdroid_peertube_apps_educDebug:
|
||||
interruptible: true
|
||||
stage: build
|
||||
script:
|
||||
- ./gradlew -Pci --console=plain :app:lintfdroid_peertube_apps_educDebug -PbuildDir=lint
|
||||
except:
|
||||
- tags
|
||||
|
||||
lintFdroid_fullDebug:
|
||||
interruptible: true
|
||||
stage: build
|
||||
script:
|
||||
- ./gradlew -Pci --console=plain :app:lintFdroid_fullDebug -PbuildDir=lint
|
||||
except:
|
||||
- tags
|
||||
|
||||
|
||||
## PROTECTED VARIABLES TO SET IN GITLAB:
|
||||
# - GITLAB_API_TOKEN: token you create on Gitlab
|
||||
# - NC_REMOTE_DIR: like https://YOUR_NEXTCLOUD/remote.php/dav/files/YOUR_USER/mastalab (no trailing slash)
|
||||
# - NC_SHARE_URL: share the folder in Nextcloud with public link and put your public link here (no trailing slash)
|
||||
# - NC_USER: nextcloud user
|
||||
# - NC_PASSWORD: nextcloud password
|
||||
## Protect all tags in Gitlab repo settings (do a wildcard, ie '*')
|
||||
# For now, it uses the assembleDebug builds, you'll need to create a job in build-and-test to create the apks, with only: - tags and add except: - tags to assembleDebug (like in debugTests)
|
||||
# In it, put something like this to get your signature key file:
|
||||
# - curl -s --output signature.jsk -u "${NC_USER}:${NC_PASSWORD}" "https://YOUR_NEXTCLOUD/remote.php/dav/files/YOUR_USER/signature.jsk"
|
||||
putApkOnTags:
|
||||
image: hatsoftwares/curl-jq:latest
|
||||
stage: tag
|
||||
retry: 2
|
||||
cache:
|
||||
key: "${CI_COMMIT_TAG}"
|
||||
paths:
|
||||
- app/build/outputs/apk/fdroid_peertube_apps_educ/debug/app-fdroid_peertube_apps_educ-debug.apk
|
||||
- app/build/outputs/apk/fdroid_full/debug/app-fdroid_full-debug.apk
|
||||
policy: pull
|
||||
script:
|
||||
- export PROJECT_API_URL="https://framagit.org/api/v4/projects/${CI_PROJECT_ID}"
|
||||
- export DESCRIPTION_URL="${PROJECT_API_URL}/repository/tags/${CI_COMMIT_TAG}"
|
||||
- export RELEASE_URL="${DESCRIPTION_URL}/release"
|
||||
- export NC_UPLOAD_URL="${NC_REMOTE_DIR}/${CI_COMMIT_TAG}"
|
||||
- export NC_DOWNLOAD_URL="${NC_SHARE_URL}/download?path=%2F${CI_COMMIT_TAG}%2F&files="
|
||||
- 'export HEADER="Private-Token: ${GITLAB_API_TOKEN}"'
|
||||
- export acadUrl="${NC_DOWNLOAD_URL}app-fdroid_peertube_apps_educ-debug.apk"
|
||||
- export fullUrl="${NC_DOWNLOAD_URL}app-fdroid_full-debug.apk"
|
||||
- 'curl -s -u "${NC_USER}:${NC_PASSWORD}" -X MKCOL "${NC_UPLOAD_URL}"'
|
||||
- 'curl -s -u "${NC_USER}:${NC_PASSWORD}" -T app/build/outputs/apk/fdroid_full/debug/app-fdroid_full-debug.apk "${NC_UPLOAD_URL}/app-fdroid_full-debug.apk"'
|
||||
- 'curl -s -u "${NC_USER}:${NC_PASSWORD}" -T app/build/outputs/apk/fdroid_peertube_apps_educ/debug/app-fdroid_peertube_apps_educ-debug.apk "${NC_UPLOAD_URL}/app-fdroid_peertube_apps_educ-debug.apk"'
|
||||
- export description=$(curl -s --header "${HEADER}" "${DESCRIPTION_URL}" | jq .release.description | sed -e 's@"@@g')
|
||||
- if [[ $description == 'null' ]]; then export METHOD="POST"; echo -e "[Get the acad version](${acadUrl})\n\n[Get the full version](${fullUrl})" > /tmp/text; fi
|
||||
- if [[ $description != 'null' ]]; then export METHOD="PUT"; echo -e "${description}\n\n[Get the acad version](${acadUrl})\n\n[Get the full version](${fullUrl})" > /tmp/text; fi
|
||||
- curl -s --request $METHOD --data-urlencode "description@/tmp/text" --header "${HEADER}" "${RELEASE_URL}"
|
||||
only:
|
||||
- tags
|
@ -10,7 +10,7 @@ Crowdin will not pick up changes in develop branch, that's why all translations
|
||||
|
||||
### Issues:
|
||||
|
||||
Issues are handled on Github at: https://github.com/stom79/TubeLab/issues, before opening an issue, please check it has not yet been submitted by someone else
|
||||
Issues are handled on Framagit at: https://framagit.org/imattau/fedilab-tube/-/issues, before opening an issue, please check it has not yet been submitted by someone else
|
||||
|
||||
### Contribution to code:
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: "androidx.navigation.safeargs"
|
||||
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.2"
|
||||
@ -9,8 +11,8 @@ android {
|
||||
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 34
|
||||
versionName "1.11.0"
|
||||
versionCode 39
|
||||
versionName "1.13.1"
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@ -40,7 +42,7 @@ android {
|
||||
}
|
||||
//boolean full_instances if set to false means TubeAcad
|
||||
productFlavors {
|
||||
fdroid_peertube_apps_educ {
|
||||
fdroid_acad {
|
||||
applicationId "app.fedilab.fedilabtube"
|
||||
resValue "string", "app_name", "TubeAcad"
|
||||
resValue "string", "app_id", "app.fedilab.fedilabtube"
|
||||
@ -51,8 +53,11 @@ android {
|
||||
buildConfigField "boolean", "sepia_search", "false"
|
||||
buildConfigField "boolean", "instance_switcher", "true"
|
||||
buildConfigField "boolean", "allow_remote_connections", "false"
|
||||
buildConfigField "boolean", "google_cast_lib", "false"
|
||||
buildConfigField "int", "cast_enabled", "0"
|
||||
buildConfigField "int", "default_theme", "2"
|
||||
}
|
||||
google_peertube_apps_educ {
|
||||
google_acad {
|
||||
applicationId "app.fedilab.fedilabtube"
|
||||
resValue "string", "app_name", "TubeAcad"
|
||||
resValue "string", "app_id", "app.fedilab.fedilabtube"
|
||||
@ -63,6 +68,9 @@ android {
|
||||
buildConfigField "boolean", "sepia_search", "false"
|
||||
buildConfigField "boolean", "instance_switcher", "true"
|
||||
buildConfigField "boolean", "allow_remote_connections", "false"
|
||||
buildConfigField "boolean", "google_cast_lib", "true"
|
||||
buildConfigField "int", "cast_enabled", "1"
|
||||
buildConfigField "int", "default_theme", "2"
|
||||
}
|
||||
fdroid_full {
|
||||
applicationId "app.fedilab.tubelab"
|
||||
@ -75,6 +83,9 @@ android {
|
||||
buildConfigField "boolean", "sepia_search", "true"
|
||||
buildConfigField "boolean", "instance_switcher", "true"
|
||||
buildConfigField "boolean", "allow_remote_connections", "true"
|
||||
buildConfigField "boolean", "google_cast_lib", "false"
|
||||
buildConfigField "int", "cast_enabled", "0"
|
||||
buildConfigField "int", "default_theme", "2"
|
||||
}
|
||||
google_full {
|
||||
applicationId "app.fedilab.tubelab"
|
||||
@ -87,6 +98,9 @@ android {
|
||||
buildConfigField "boolean", "sepia_search", "true"
|
||||
buildConfigField "boolean", "instance_switcher", "true"
|
||||
buildConfigField "boolean", "allow_remote_connections", "true"
|
||||
buildConfigField "boolean", "google_cast_lib", "true"
|
||||
buildConfigField "int", "cast_enabled", "1"
|
||||
buildConfigField "int", "default_theme", "2"
|
||||
}
|
||||
queermotion {
|
||||
applicationId "org.queermotion.peertube"
|
||||
@ -99,6 +113,9 @@ android {
|
||||
buildConfigField "boolean", "sepia_search", "false"
|
||||
buildConfigField "boolean", "instance_switcher", "false"
|
||||
buildConfigField "boolean", "allow_remote_connections", "false"
|
||||
buildConfigField "boolean", "google_cast_lib", "false"
|
||||
buildConfigField "int", "cast_enabled", "0"
|
||||
buildConfigField "int", "default_theme", "2"
|
||||
}
|
||||
bittube {
|
||||
applicationId "app.fedilab.bittube"
|
||||
@ -111,33 +128,37 @@ android {
|
||||
buildConfigField "boolean", "sepia_search", "false"
|
||||
buildConfigField "boolean", "instance_switcher", "true"
|
||||
buildConfigField "boolean", "allow_remote_connections", "false"
|
||||
buildConfigField "boolean", "google_cast_lib", "true"
|
||||
buildConfigField "int", "cast_enabled", "1"
|
||||
buildConfigField "int", "default_theme", "1"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
fdroid_peertube_apps_educ {
|
||||
res.srcDirs = ['src/main/res', 'src/acad/res']
|
||||
java.srcDirs = ['src/main/java', 'src/acad/java']
|
||||
|
||||
fdroid_acad {
|
||||
res.srcDirs = ['src/main/res', 'src/acad/res', 'src/no_google_cast_lib/res']
|
||||
java.srcDirs = ['src/main/java', 'src/acad/java', 'src/no_google_donation/java', 'src/no_google_cast_lib/java']
|
||||
}
|
||||
google_peertube_apps_educ {
|
||||
res.srcDirs = ['src/main/res', 'src/acad/res']
|
||||
java.srcDirs = ['src/main/java', 'src/acad/java']
|
||||
google_acad {
|
||||
res.srcDirs = ['src/main/res', 'src/acad/res', 'src/google_cast_lib/res']
|
||||
java.srcDirs = ['src/main/java', 'src/acad/java', 'src/no_google_donation/java', 'src/google_cast_lib/java']
|
||||
}
|
||||
fdroid_full {
|
||||
res.srcDirs = ['src/main/res', 'src/full/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java']
|
||||
res.srcDirs = ['src/main/res', 'src/full/res', 'src/no_google_cast_lib/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java', 'src/no_google_cast_lib/java']
|
||||
}
|
||||
google_full {
|
||||
res.srcDirs = ['src/main/res', 'src/full/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java']
|
||||
res.srcDirs = ['src/main/res', 'src/full/res', 'src/google_donation/res', 'src/google_cast_lib/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java', 'src/google_donation/java', 'src/google_cast_lib/java']
|
||||
}
|
||||
queermotion {
|
||||
res.srcDirs = ['src/main/res', 'src/queermotion/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java']
|
||||
res.srcDirs = ['src/main/res', 'src/queermotion/res', 'src/no_google_cast_lib/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java', 'src/no_google_cast_lib/java']
|
||||
}
|
||||
bittube {
|
||||
res.srcDirs = ['src/main/res', 'src/bittube/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java']
|
||||
res.srcDirs = ['src/main/res', 'src/bittube/res', 'src/google_cast_lib/res']
|
||||
java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java', 'src/google_cast_lib/java']
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,6 +187,7 @@ dependencies {
|
||||
implementation 'androidx.browser:browser:1.3.0'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation project(path: ':torrentStream')
|
||||
implementation project(path: ':frostwire-jlibtorrent')
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
@ -177,6 +199,7 @@ dependencies {
|
||||
implementation "com.github.mabbas007:TagsEditText:1.0.5"
|
||||
implementation "com.github.bumptech.glide:glide:4.11.0"
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:4.11.0"
|
||||
implementation 'jp.wasabeef:glide-transformations:4.0.0'
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "net.gotev:uploadservice:4.5.1"
|
||||
implementation "net.gotev:uploadservice-okhttp:4.5.1"
|
||||
@ -194,15 +217,34 @@ dependencies {
|
||||
implementation "androidx.work:work-runtime:2.4.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.4.0"
|
||||
|
||||
//custom cast feature
|
||||
implementation 'jp.wasabeef:glide-transformations:4.0.0'
|
||||
implementation 'su.litvak.chromecast:api-v2:0.11.3'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
|
||||
implementation 'org.slf4j:slf4j-simple:1.7.30'
|
||||
|
||||
//************ DONATION GOOGLE ONLY **************//
|
||||
google_fullImplementation "com.android.billingclient:billing:3.0.2"
|
||||
|
||||
fdroid_peertube_apps_educImplementation 'org.matomo.sdk:tracker:4.1.2'
|
||||
google_peertube_apps_educImplementation 'org.matomo.sdk:tracker:4.1.2'
|
||||
//************ MATOMO --> acad instances only **************//
|
||||
|
||||
fdroid_acadImplementation 'org.matomo.sdk:tracker:4.1.2'
|
||||
google_acadImplementation 'org.matomo.sdk:tracker:4.1.2'
|
||||
|
||||
//************ CAST **************///
|
||||
|
||||
//---> Google libs (google_full + bittube)
|
||||
google_acadImplementation "androidx.mediarouter:mediarouter:1.2.1"
|
||||
google_acadImplementation 'com.google.android.gms:play-services-cast-framework:19.0.0'
|
||||
google_fullImplementation "androidx.mediarouter:mediarouter:1.2.1"
|
||||
google_fullImplementation 'com.google.android.gms:play-services-cast-framework:19.0.0'
|
||||
bittubeImplementation "androidx.mediarouter:mediarouter:1.2.1"
|
||||
bittubeImplementation 'com.google.android.gms:play-services-cast-framework:19.0.0'
|
||||
|
||||
//----> Other flavors
|
||||
fdroid_acadImplementation 'su.litvak.chromecast:api-v2:0.11.3'
|
||||
fdroid_acadImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
|
||||
fdroid_acadImplementation 'org.slf4j:slf4j-simple:1.7.30'
|
||||
fdroid_fullImplementation 'su.litvak.chromecast:api-v2:0.11.3'
|
||||
fdroid_fullImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
|
||||
fdroid_fullImplementation 'org.slf4j:slf4j-simple:1.7.30'
|
||||
queermotionImplementation 'su.litvak.chromecast:api-v2:0.11.3'
|
||||
queermotionImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
|
||||
queermotionImplementation 'org.slf4j:slf4j-simple:1.7.30'
|
||||
|
||||
}
|
45
app/src/bittube/AndroidManifest.xml
Normal file
45
app/src/bittube/AndroidManifest.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<?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="app.fedilab.fedilabtube">
|
||||
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<application
|
||||
android:name=".FedilabTube"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:replace="android:allowBackup">
|
||||
|
||||
<activity
|
||||
android:name=".PeertubeActivity"
|
||||
tools:node="mergeOnlyAttributes">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- The app is a good candidate for URL in https://domain.name/videos/watch/xxxxx-->
|
||||
<data
|
||||
android:host="*"
|
||||
android:pathPrefix="/videos/watch/"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".PeertubeActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".expandedcontrols.ExpandedControlsActivity"
|
||||
android:theme="@style/AppThemeNoActionBar"
|
||||
/>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="app.fedilab.fedilabtube.provider.CastOptionsProvider" />
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,41 @@
|
||||
package app.fedilab.fedilabtube;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import app.fedilab.fedilabtube.databinding.ActivityMainBinding;
|
||||
|
||||
public class BaseMainActivity extends AppCompatActivity {
|
||||
|
||||
protected ActivityMainBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
View view = binding.getRoot();
|
||||
setContentView(view);
|
||||
}
|
||||
|
||||
//Method for discovering cast devices
|
||||
public void discoverCast() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
package app.fedilab.fedilabtube;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.gms.cast.MediaInfo;
|
||||
import com.google.android.gms.cast.MediaMetadata;
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||
import com.google.android.gms.cast.framework.CastContext;
|
||||
import com.google.android.gms.cast.framework.CastSession;
|
||||
import com.google.android.gms.cast.framework.SessionManagerListener;
|
||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||
import com.google.android.gms.common.images.WebImage;
|
||||
|
||||
import app.fedilab.fedilabtube.client.data.VideoData;
|
||||
import app.fedilab.fedilabtube.databinding.ActivityPeertubeBinding;
|
||||
import app.fedilab.fedilabtube.helper.Helper;
|
||||
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
public class BasePeertubeActivity extends AppCompatActivity {
|
||||
|
||||
protected ActivityPeertubeBinding binding;
|
||||
protected VideoData.Video peertube;
|
||||
protected SimpleExoPlayer player;
|
||||
protected String videoURL;
|
||||
protected String subtitlesStr;
|
||||
|
||||
private CastContext mCastContext;
|
||||
private CastSession mCastSession;
|
||||
private SessionManagerListener<CastSession> mSessionManagerListener;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
|
||||
View view = binding.getRoot();
|
||||
setContentView(view);
|
||||
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
|
||||
int search_cast = sharedpreferences.getInt(getString(R.string.set_cast_choice), BuildConfig.cast_enabled);
|
||||
if (search_cast == 1) {
|
||||
setupCastListener();
|
||||
mCastContext = CastContext.getSharedInstance(this);
|
||||
mCastSession = mCastContext.getSessionManager().getCurrentCastSession();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected void loadCast() {
|
||||
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
|
||||
|
||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, peertube.getTitle());
|
||||
movieMetadata.putString(MediaMetadata.KEY_ARTIST, peertube.getAccount().getDisplayName());
|
||||
if (subtitlesStr != null) {
|
||||
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, subtitlesStr);
|
||||
}
|
||||
movieMetadata.addImage(new WebImage(Uri.parse("https://" + peertube.getChannel().getHost() + peertube.getPreviewPath())));
|
||||
MediaInfo mediaInfo = new MediaInfo.Builder(videoURL)
|
||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||
.setMetadata(movieMetadata)
|
||||
.setStreamDuration(peertube.getDuration() * 1000)
|
||||
.build();
|
||||
if (mCastSession != null) {
|
||||
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
|
||||
remoteMediaClient.load(mediaInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setupCastListener() {
|
||||
mSessionManagerListener = new SessionManagerListener<CastSession>() {
|
||||
@Override
|
||||
public void onSessionStarting(CastSession castSession) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionStarted(CastSession castSession, String s) {
|
||||
onApplicationConnected(castSession, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionStartFailed(CastSession castSession, int i) {
|
||||
onApplicationDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionEnding(CastSession castSession) {
|
||||
onApplicationDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionEnded(CastSession castSession, int i) {
|
||||
onApplicationDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionResuming(CastSession castSession, String s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionResumed(CastSession castSession, boolean b) {
|
||||
onApplicationConnected(castSession, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionResumeFailed(CastSession castSession, int i) {
|
||||
onApplicationDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionSuspended(CastSession castSession, int i) {
|
||||
onApplicationDisconnected();
|
||||
}
|
||||
|
||||
private void onApplicationConnected(CastSession castSession, boolean hide) {
|
||||
mCastSession = castSession;
|
||||
supportInvalidateOptionsMenu();
|
||||
player.setPlayWhenReady(false);
|
||||
if (hide) {
|
||||
binding.doubleTapPlayerView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
binding.minController.castMiniController.setVisibility(View.VISIBLE);
|
||||
loadCast();
|
||||
}
|
||||
|
||||
private void onApplicationDisconnected() {
|
||||
binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
|
||||
binding.minController.castMiniController.setVisibility(View.GONE);
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
mCastContext.getSessionManager().addSessionManagerListener(
|
||||
mSessionManagerListener, CastSession.class);
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
mCastContext.getSessionManager().removeSessionManagerListener(
|
||||
mSessionManagerListener, CastSession.class);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.video_menu, menu);
|
||||
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
|
||||
menu,
|
||||
R.id.media_route_button);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Google LLC. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package app.fedilab.fedilabtube.expandedcontrols;
|
||||
|
||||
import android.view.Menu;
|
||||
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity;
|
||||
|
||||
import app.fedilab.fedilabtube.R;
|
||||
|
||||
|
||||
public class ExpandedControlsActivity extends ExpandedControllerActivity {
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.video_menu, menu);
|
||||
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_button);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package app.fedilab.fedilabtube.provider;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.gms.cast.framework.CastOptions;
|
||||
import com.google.android.gms.cast.framework.OptionsProvider;
|
||||
import com.google.android.gms.cast.framework.SessionProvider;
|
||||
import com.google.android.gms.cast.framework.media.CastMediaOptions;
|
||||
import com.google.android.gms.cast.framework.media.NotificationOptions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.fedilabtube.BuildConfig;
|
||||
import app.fedilab.fedilabtube.expandedcontrols.ExpandedControlsActivity;
|
||||
import app.fedilab.fedilabtube.helper.Helper;
|
||||
|
||||
|
||||
public class CastOptionsProvider implements OptionsProvider {
|
||||
|
||||
|
||||
@Override
|
||||
public CastOptions getCastOptions(Context context) {
|
||||
NotificationOptions notificationOptions = new NotificationOptions.Builder()
|
||||
.setTargetActivityClassName(ExpandedControlsActivity.class.getName())
|
||||
.build();
|
||||
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
|
||||
.setNotificationOptions(notificationOptions)
|
||||
.setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
|
||||
.build();
|
||||
return new CastOptions.Builder()
|
||||
.setReceiverApplicationId(BuildConfig.FLAVOR.compareTo("bittube") == 0 ? Helper.CAST_ID_BITTUBE : Helper.CAST_ID)
|
||||
.setCastMediaOptions(mediaOptions)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SessionProvider> getAdditionalSessionProviders(Context context) {
|
||||
return null;
|
||||
}
|
||||
}
|
20
app/src/google_cast_lib/res/layout/min_controller.xml
Normal file
20
app/src/google_cast_lib/res/layout/min_controller.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?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:id="@+id/castMiniController"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:visibility="gone">
|
||||
|
||||
<fragment
|
||||
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
9
app/src/google_cast_lib/res/menu/video_menu.xml
Normal file
9
app/src/google_cast_lib/res/menu/video_menu.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/media_route_button"
|
||||
android:title="@string/cast"
|
||||
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
@ -0,0 +1,278 @@
|
||||
package app.fedilab.fedilabtube;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.android.billingclient.api.AcknowledgePurchaseParams;
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingClientStateListener;
|
||||
import com.android.billingclient.api.BillingResult;
|
||||
import com.android.billingclient.api.ConsumeParams;
|
||||
import com.android.billingclient.api.ConsumeResponseListener;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||
import com.android.billingclient.api.SkuDetailsParams;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.fedilabtube.databinding.ActivityDonationBinding;
|
||||
|
||||
import app.fedilab.fedilabtube.fragment.MySubscriptionDonationsFragment;
|
||||
import app.fedilab.fedilabtube.fragment.DonationsFragment;
|
||||
|
||||
|
||||
public class DonationActivity extends AppCompatActivity implements PurchasesUpdatedListener {
|
||||
|
||||
|
||||
DonationsFragment donationsFragment;
|
||||
DonationsFragment subscriptionDonationsFragment;
|
||||
MySubscriptionDonationsFragment mySubscriptionDonationsFragment;
|
||||
private ActivityDonationBinding binding;
|
||||
private BillingClient billingClient;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityDonationBinding.inflate(getLayoutInflater());
|
||||
View view = binding.getRoot();
|
||||
setContentView(view);
|
||||
billingClient = BillingClient.newBuilder(this)
|
||||
.setListener(this)
|
||||
.enablePendingPurchases()
|
||||
.build();
|
||||
billingClient.startConnection(new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(@NotNull BillingResult billingResult) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
// The BillingClient is ready. You can query purchases here.
|
||||
donationsFragment.initialized(billingClient);
|
||||
subscriptionDonationsFragment.initialized(billingClient);
|
||||
|
||||
List<Purchase> purchases = queryPurchases();
|
||||
if (purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
if (!purchase.isAutoRenewing()) {
|
||||
ConsumeParams consumeParams =
|
||||
ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchase.getPurchaseToken())
|
||||
.build();
|
||||
|
||||
ConsumeResponseListener listener = (billingResult1, purchaseToken) -> {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
// Handle the success of the consume operation.
|
||||
}
|
||||
};
|
||||
billingClient.consumeAsync(consumeParams, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
// Try to restart the connection on the next request to
|
||||
// Google Play by calling the startConnection() method.
|
||||
}
|
||||
});
|
||||
if (getSupportActionBar() != null)
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
donationsFragment = new DonationsFragment();
|
||||
Bundle bundle1 = new Bundle();
|
||||
bundle1.putSerializable("isSubscriptions", false);
|
||||
donationsFragment.setArguments(bundle1);
|
||||
|
||||
|
||||
subscriptionDonationsFragment = new DonationsFragment();
|
||||
Bundle bundle2 = new Bundle();
|
||||
bundle2.putSerializable("isSubscriptions", true);
|
||||
subscriptionDonationsFragment.setArguments(bundle2);
|
||||
|
||||
mySubscriptionDonationsFragment = new MySubscriptionDonationsFragment();
|
||||
|
||||
binding.tablayout.addTab(binding.tablayout.newTab().setText(getString(R.string.one_time)));
|
||||
binding.tablayout.addTab(binding.tablayout.newTab().setText(getString(R.string.subscriptions)));
|
||||
binding.tablayout.addTab(binding.tablayout.newTab().setText(getString(R.string.my_subscriptions)));
|
||||
binding.viewpager.setOffscreenPageLimit(3);
|
||||
|
||||
PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
|
||||
binding.viewpager.setAdapter(mPagerAdapter);
|
||||
binding.viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
TabLayout.Tab tab = binding.tablayout.getTabAt(position);
|
||||
if (tab != null)
|
||||
tab.select();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
binding.tablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
binding.viewpager.setCurrentItem(tab.getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private List<Purchase> queryPurchases() {
|
||||
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
|
||||
List<Purchase> purchases = purchasesResult.getPurchasesList();
|
||||
List<String> isSubscriptions = new ArrayList<>();
|
||||
HashMap<String, Purchase> map = new HashMap<>();
|
||||
if (purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
try {
|
||||
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
|
||||
JSONObject purchaseJson = new JSONObject(purchase.getOriginalJson());
|
||||
String productId = purchaseJson.getString("productId");
|
||||
isSubscriptions.add(productId);
|
||||
map.put(productId, purchase);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
SkuDetailsParams.Builder paramsSub = SkuDetailsParams.newBuilder();
|
||||
paramsSub.setSkusList(isSubscriptions).setType(BillingClient.SkuType.SUBS);
|
||||
billingClient.querySkuDetailsAsync(paramsSub.build(),
|
||||
(billingResult2, skuDetailsList) -> mySubscriptionDonationsFragment.initialized(skuDetailsList, map, billingClient));
|
||||
} else {
|
||||
mySubscriptionDonationsFragment.initialized(new ArrayList<>(), map, billingClient);
|
||||
}
|
||||
return purchases;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
|
||||
String message;
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
|
||||
&& purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
if (!purchase.isAutoRenewing()) {
|
||||
ConsumeParams consumeParams =
|
||||
ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchase.getPurchaseToken())
|
||||
.build();
|
||||
|
||||
ConsumeResponseListener listener = (billingResult1, purchaseToken) -> {
|
||||
};
|
||||
billingClient.consumeAsync(consumeParams, listener);
|
||||
} else {
|
||||
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
|
||||
if (!purchase.isAcknowledged()) {
|
||||
AcknowledgePurchaseParams acknowledgePurchaseParams =
|
||||
AcknowledgePurchaseParams.newBuilder()
|
||||
.setPurchaseToken(purchase.getPurchaseToken())
|
||||
.build();
|
||||
billingClient.acknowledgePurchase(acknowledgePurchaseParams, b -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
queryPurchases();
|
||||
}
|
||||
}
|
||||
message = getString(R.string.donation_succeeded_null);
|
||||
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
|
||||
message = getString(R.string.donation_cancelled);
|
||||
} else {
|
||||
message = getString(R.string.toast_error);
|
||||
}
|
||||
View parentLayout = findViewById(android.R.id.content);
|
||||
Snackbar snackbar = Snackbar.make(parentLayout, message, Snackbar.LENGTH_INDEFINITE);
|
||||
snackbar.setAction(R.string.close, view -> snackbar.dismiss());
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pager adapter for the 2 fragments
|
||||
*/
|
||||
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
ScreenSlidePagerAdapter(FragmentManager fm) {
|
||||
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
if (position == 0) {
|
||||
return donationsFragment;
|
||||
} else if (position == 1) {
|
||||
return subscriptionDonationsFragment;
|
||||
} else {
|
||||
return mySubscriptionDonationsFragment;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package app.fedilab.fedilabtube.drawable;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingFlowParams;
|
||||
import com.android.billingclient.api.SkuDetails;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import app.fedilab.fedilabtube.DonationActivity;
|
||||
import app.fedilab.fedilabtube.R;
|
||||
import app.fedilab.fedilabtube.databinding.DrawerDonationBinding;
|
||||
|
||||
|
||||
public class DonationButtonAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private final List<SkuDetails> skuDetails;
|
||||
private final BillingClient billingClient;
|
||||
private Context context;
|
||||
private final boolean isSubscription;
|
||||
|
||||
public DonationButtonAdapter(List<SkuDetails> skuDetails, BillingClient billingClient, boolean subscription) {
|
||||
this.isSubscription = subscription;
|
||||
this.skuDetails = skuDetails;
|
||||
this.billingClient = billingClient;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
context = parent.getContext();
|
||||
DrawerDonationBinding itemBinding = DrawerDonationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new ViewHolder(itemBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
||||
final ViewHolder holder = (ViewHolder) viewHolder;
|
||||
SkuDetails skuDetail = skuDetails.get(position);
|
||||
String currency = skuDetail.getPriceCurrencyCode();
|
||||
String price = skuDetail.getPrice();
|
||||
if (isSubscription) {
|
||||
holder.binding.buttonDonation.setText(String.format(Locale.getDefault(), "%s %s / %s", price, currency, context.getString(R.string.month)));
|
||||
} else {
|
||||
holder.binding.buttonDonation.setText(String.format(Locale.getDefault(), "%s %s", price, currency));
|
||||
}
|
||||
|
||||
holder.binding.buttonDonation.setOnClickListener(v -> {
|
||||
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
|
||||
.setSkuDetails(skuDetail)
|
||||
.build();
|
||||
billingClient.launchBillingFlow((DonationActivity) context, billingFlowParams);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return skuDetails.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
DrawerDonationBinding binding;
|
||||
|
||||
ViewHolder(DrawerDonationBinding itemView) {
|
||||
super(itemView.getRoot());
|
||||
binding = itemView;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package app.fedilab.fedilabtube.drawable;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.ConsumeParams;
|
||||
import com.android.billingclient.api.ConsumeResponseListener;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.SkuDetails;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import app.fedilab.fedilabtube.R;
|
||||
import app.fedilab.fedilabtube.databinding.DrawerMyDonationBinding;
|
||||
import es.dmoral.toasty.Toasty;
|
||||
|
||||
|
||||
public class DonationHistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private final List<SkuDetails> skuDetailsList;
|
||||
private final BillingClient billingClient;
|
||||
private Context context;
|
||||
private final HashMap<String, Purchase> map;
|
||||
|
||||
public DonationHistoryAdapter(List<SkuDetails> SkuDetailsList, HashMap<String, Purchase> map, BillingClient billingClient) {
|
||||
this.skuDetailsList = SkuDetailsList;
|
||||
this.billingClient = billingClient;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
context = parent.getContext();
|
||||
DrawerMyDonationBinding itemBinding = DrawerMyDonationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new ViewHolder(itemBinding);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
||||
final ViewHolder holder = (ViewHolder) viewHolder;
|
||||
SkuDetails skuDetails = skuDetailsList.get(position);
|
||||
holder.binding.productTitle.setText(skuDetails.getTitle());
|
||||
holder.binding.productName.setText(skuDetails.getDescription());
|
||||
holder.binding.productInfo.setText(skuDetails.getOriginalPrice());
|
||||
|
||||
holder.binding.cancelDonation.setOnClickListener(v -> {
|
||||
|
||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
|
||||
dialogBuilder.setMessage(R.string.cancel_subscription_confirm);
|
||||
|
||||
dialogBuilder.setPositiveButton(R.string.cancel_subscription, (dialog, id) -> {
|
||||
JSONObject skudetailsJson;
|
||||
try {
|
||||
skudetailsJson = new JSONObject(skuDetails.getOriginalJson());
|
||||
String productId = skudetailsJson.getString("productId");
|
||||
if (map.containsKey(productId)) {
|
||||
Purchase purchase = map.get(productId);
|
||||
if (purchase != null) {
|
||||
ConsumeParams consumeParams =
|
||||
ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchase.getPurchaseToken())
|
||||
.build();
|
||||
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
|
||||
ConsumeResponseListener listener = (billingResult1, purchaseToken) -> {
|
||||
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
Toasty.success(context, context.getString(R.string.subscription_cancelled), Toasty.LENGTH_LONG).show();
|
||||
}
|
||||
skuDetailsList.remove(skuDetails);
|
||||
notifyDataSetChanged();
|
||||
};
|
||||
billingClient.consumeAsync(consumeParams, listener);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
|
||||
AlertDialog alertDialogLogoutAccount = dialogBuilder.create();
|
||||
alertDialogLogoutAccount.show();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return skuDetailsList.size();
|
||||
}
|
||||
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
DrawerMyDonationBinding binding;
|
||||
|
||||
ViewHolder(DrawerMyDonationBinding itemView) {
|
||||
super(itemView.getRoot());
|
||||
binding = itemView;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package app.fedilab.fedilabtube.fragment;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.SkuDetailsParams;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.fedilabtube.R;
|
||||
import app.fedilab.fedilabtube.databinding.FragmentDonationsBinding;
|
||||
import app.fedilab.fedilabtube.drawable.DonationButtonAdapter;
|
||||
|
||||
|
||||
public class DonationsFragment extends Fragment {
|
||||
|
||||
public static final String[] donations = {"1", "2", "5", "10"};
|
||||
private FragmentDonationsBinding binding;
|
||||
private View rootView;
|
||||
private Context context;
|
||||
private boolean isSubscriptions;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentDonationsBinding.inflate(LayoutInflater.from(context));
|
||||
rootView = binding.getRoot();
|
||||
context = getContext();
|
||||
Bundle bundle = this.getArguments();
|
||||
if (bundle != null) {
|
||||
isSubscriptions = bundle.getBoolean("isSubscriptions", false);
|
||||
}
|
||||
int donationText;
|
||||
if (isSubscriptions) {
|
||||
donationText = R.string.recurrent_donation_text;
|
||||
} else {
|
||||
donationText = R.string.one_time_donation_text;
|
||||
}
|
||||
binding.donationText.setText(donationText);
|
||||
binding.loader.setVisibility(View.VISIBLE);
|
||||
binding.lvProducts.setVisibility(View.GONE);
|
||||
return rootView;
|
||||
}
|
||||
|
||||
public void initialized(BillingClient bc) {
|
||||
|
||||
|
||||
List<String> donationsList = new ArrayList<>();
|
||||
for (String val : donations) {
|
||||
donationsList.add("tubelab_donation_" + val + (isSubscriptions ? "_s" : ""));
|
||||
}
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
|
||||
if (isSubscriptions) {
|
||||
params.setSkusList(donationsList).setType(BillingClient.SkuType.SUBS);
|
||||
} else {
|
||||
params.setSkusList(donationsList).setType(BillingClient.SkuType.INAPP);
|
||||
}
|
||||
bc.querySkuDetailsAsync(params.build(),
|
||||
(billingResult, skuDetailsList) -> {
|
||||
binding.loader.setVisibility(View.GONE);
|
||||
binding.lvProducts.setVisibility(View.VISIBLE);
|
||||
if (skuDetailsList != null) {
|
||||
Collections.sort(skuDetailsList, (obj1, obj2) -> obj1.getPrice().compareTo(obj2.getPrice()));
|
||||
}
|
||||
DonationButtonAdapter donationButtonAdapter = new DonationButtonAdapter(skuDetailsList, bc, isSubscriptions);
|
||||
binding.lvProducts.setAdapter(donationButtonAdapter);
|
||||
binding.lvProducts.setLayoutManager(new LinearLayoutManager(context));
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
rootView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle saveInstance) {
|
||||
super.onCreate(saveInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
this.context = context;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package app.fedilab.fedilabtube.fragment;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.SkuDetails;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.fedilabtube.databinding.FragmentMyDonationsBinding;
|
||||
import app.fedilab.fedilabtube.drawable.DonationHistoryAdapter;
|
||||
|
||||
public class MySubscriptionDonationsFragment extends Fragment {
|
||||
|
||||
private FragmentMyDonationsBinding binding;
|
||||
private View rootView;
|
||||
private Context context;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentMyDonationsBinding.inflate(LayoutInflater.from(context));
|
||||
rootView = binding.getRoot();
|
||||
context = getContext();
|
||||
binding.loader.setVisibility(View.VISIBLE);
|
||||
binding.lvPurchases.setVisibility(View.GONE);
|
||||
return rootView;
|
||||
}
|
||||
|
||||
public void initialized(List<SkuDetails> skuDetailsList, HashMap<String, Purchase> map, BillingClient bc) {
|
||||
binding.loader.setVisibility(View.GONE);
|
||||
binding.lvPurchases.setVisibility(View.VISIBLE);
|
||||
|
||||
DonationHistoryAdapter donationHistoryAdapter = new DonationHistoryAdapter(skuDetailsList, map, bc);
|
||||
binding.lvPurchases.setAdapter(donationHistoryAdapter);
|
||||
binding.lvPurchases.setLayoutManager(new LinearLayoutManager(context));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
rootView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle saveInstance) {
|
||||
super.onCreate(saveInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
this.context = context;
|
||||
}
|
||||
}
|
60
app/src/google_donation/res/layout/activity_donation.xml
Normal file
60
app/src/google_donation/res/layout/activity_donation.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2021 Thomas Schneider
|
||||
|
||||
This file is a part of TubeLab
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
<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"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".ShowChannelActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
app:theme="@style/ThemeOverlay.AppCompat.ActionBar">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
app:contentScrim="?attr/colorPrimary"
|
||||
app:expandedTitleMarginEnd="64dp"
|
||||
app:expandedTitleMarginStart="48dp"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tablayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:tabGravity="fill"
|
||||
app:tabMode="fixed"
|
||||
app:tabSelectedTextColor="?colorAccent"
|
||||
app:tabTextColor="@android:color/white" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
23
app/src/google_donation/res/layout/drawer_donation.xml
Normal file
23
app/src/google_donation/res/layout/drawer_donation.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?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">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_donation"
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:paddingStart="40dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingEnd="40dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:textSize="25sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
54
app/src/google_donation/res/layout/drawer_my_donation.xml
Normal file
54
app/src/google_donation/res/layout/drawer_my_donation.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView 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"
|
||||
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/product_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cancel_donation"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/product_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cancel_donation"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/product_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/product_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cancel_donation"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/product_name" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cancel_donation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/cancel"
|
||||
android:src="@drawable/ic_baseline_delete_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
59
app/src/google_donation/res/layout/fragment_donations.xml
Normal file
59
app/src/google_donation/res/layout/fragment_donations.xml
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2021 Thomas Schneider
|
||||
|
||||
This file is a part of TubeLab
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/fab_margin"
|
||||
android:paddingRight="@dimen/fab_margin">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="20dp"
|
||||
android:gravity="center"
|
||||
android:id="@+id/donation_text" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/lv_products"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@null"
|
||||
android:scrollbars="none" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Main Loader -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/loader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.github.ybq.android.spinkit.SpinKitView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:SpinKit_Color="?colorAccent" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
59
app/src/google_donation/res/layout/fragment_my_donations.xml
Normal file
59
app/src/google_donation/res/layout/fragment_my_donations.xml
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2021 Thomas Schneider
|
||||
|
||||
This file is a part of TubeLab
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/fab_margin"
|
||||
android:paddingRight="@dimen/fab_margin">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/donations_description" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/lv_purchases"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@null"
|
||||
android:scrollbars="none" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Main Loader -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/loader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.github.ybq.android.spinkit.SpinKitView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:SpinKit_Color="?colorAccent" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
@ -3,7 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="app.fedilab.fedilabtube">
|
||||
|
||||
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<application
|
||||
android:name=".FedilabTube"
|
||||
android:allowBackup="false"
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".PeertubeActivity"
|
||||
tools:node="mergeOnlyAttributes">
|
||||
tools:node="merge">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@ -28,7 +28,25 @@
|
||||
android:pathPrefix="/videos/watch/"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".PeertubeActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".expandedcontrols.ExpandedControlsActivity"
|
||||
android:theme="@style/AppThemeNoActionBar"
|
||||
|
||||
/>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="app.fedilab.fedilabtube.provider.CastOptionsProvider" />
|
||||
|
||||
<activity
|
||||
android:name=".DonationActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/support_the_app"
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
</application>
|
||||
</manifest>
|
@ -61,7 +61,7 @@ public class AboutActivity extends AppCompatActivity {
|
||||
content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(AboutActivity.this, Helper.getColorAccent())), 0, content.length(), 0);
|
||||
developer_mastodon.setText(content);
|
||||
developer_mastodon.setOnClickListener(v -> {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://toot.fedilab.app/@Tubelab"));
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://toot.fedilab.app/@apps"));
|
||||
startActivity(browserIntent);
|
||||
});
|
||||
|
||||
@ -80,15 +80,6 @@ public class AboutActivity extends AppCompatActivity {
|
||||
TextView app_name = findViewById(R.id.app_name);
|
||||
app_name.setText(R.string.app_name);
|
||||
|
||||
//Developer Github
|
||||
TextView github = findViewById(R.id.github);
|
||||
content = new SpannableString(github.getText().toString());
|
||||
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
|
||||
github.setText(content);
|
||||
github.setOnClickListener(v -> {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/stom79"));
|
||||
startActivity(browserIntent);
|
||||
});
|
||||
|
||||
//Developer Framagit
|
||||
TextView framagit = findViewById(R.id.framagit);
|
||||
@ -100,15 +91,6 @@ public class AboutActivity extends AppCompatActivity {
|
||||
startActivity(browserIntent);
|
||||
});
|
||||
|
||||
//Developer Codeberg
|
||||
TextView codeberg = findViewById(R.id.codeberg);
|
||||
content = new SpannableString(codeberg.getText().toString());
|
||||
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
|
||||
codeberg.setText(content);
|
||||
codeberg.setOnClickListener(v -> {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://codeberg.org/tom79"));
|
||||
startActivity(browserIntent);
|
||||
});
|
||||
|
||||
LinearLayout donation_container = findViewById(R.id.donation_container);
|
||||
if (BuildConfig.google_restriction || !BuildConfig.full_instances) {
|
||||
|
@ -19,6 +19,8 @@ import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
@ -52,6 +54,8 @@ import app.fedilab.fedilabtube.helper.SwitchAccountHelper;
|
||||
import app.fedilab.fedilabtube.sqlite.AccountDAO;
|
||||
import app.fedilab.fedilabtube.sqlite.Sqlite;
|
||||
|
||||
import static app.fedilab.fedilabtube.MainActivity.badgeCount;
|
||||
|
||||
|
||||
public class AccountActivity extends AppCompatActivity {
|
||||
|
||||
@ -112,9 +116,13 @@ public class AccountActivity extends AppCompatActivity {
|
||||
});
|
||||
|
||||
|
||||
|
||||
TabLayout.Tab notificationTab = binding.accountTabLayout.newTab();
|
||||
if (Helper.isLoggedIn(AccountActivity.this)) {
|
||||
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_notifications)));
|
||||
if (badgeCount > 0) {
|
||||
binding.accountTabLayout.addTab(notificationTab.setText(getString(R.string.title_notifications) + " (" + badgeCount + ")"));
|
||||
} else {
|
||||
binding.accountTabLayout.addTab(notificationTab.setText(getString(R.string.title_notifications)));
|
||||
}
|
||||
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_muted)));
|
||||
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_channel)));
|
||||
|
||||
@ -159,10 +167,30 @@ public class AccountActivity extends AppCompatActivity {
|
||||
fragment = (Fragment) binding.accountViewpager.getAdapter().instantiateItem(binding.accountViewpager, tab.getPosition());
|
||||
switch (tab.getPosition()) {
|
||||
case 0:
|
||||
if (badgeCount > 0) {
|
||||
android.app.AlertDialog.Builder builder;
|
||||
builder = new android.app.AlertDialog.Builder(AccountActivity.this);
|
||||
builder.setMessage(R.string.mark_all_notifications_as_read_confirm);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(R.string.mark_all_as_read, (dialog, which) -> {
|
||||
new Thread(() -> {
|
||||
new RetrofitPeertubeAPI(AccountActivity.this).markAllAsRead();
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
badgeCount = 0;
|
||||
Runnable myRunnable = () -> binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications));
|
||||
mainHandler.post(myRunnable);
|
||||
}).start();
|
||||
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
} else {
|
||||
if (fragment != null) {
|
||||
DisplayNotificationsFragment displayNotificationsFragment = ((DisplayNotificationsFragment) fragment);
|
||||
displayNotificationsFragment.scrollToTop();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (fragment != null) {
|
||||
@ -195,6 +223,14 @@ public class AccountActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateCounter() {
|
||||
if (badgeCount > 0) {
|
||||
binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications) + " (" + badgeCount + ")");
|
||||
} else {
|
||||
binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
@ -210,6 +246,7 @@ public class AccountActivity extends AppCompatActivity {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_add_account) {
|
||||
SwitchAccountHelper.switchDialog(AccountActivity.this, true);
|
||||
@ -255,5 +292,11 @@ public class AccountActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -65,7 +65,7 @@ public class BaseFedilabTube extends MultiDexApplication {
|
||||
|
||||
MultiDex.install(BaseFedilabTube.this);
|
||||
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
int themePref = sharedpreferences.getInt(Helper.SET_THEME, Helper.DEFAULT_MODE);
|
||||
int themePref = sharedpreferences.getInt(Helper.SET_THEME, BuildConfig.default_theme);
|
||||
ThemeHelper.switchTo(themePref);
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@ import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.util.Log;
|
||||
import android.util.Patterns;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -37,17 +38,16 @@ import androidx.core.content.ContextCompat;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.fedilabtube.client.mastodon.RetrofitMastodonAPI;
|
||||
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
|
||||
import app.fedilab.fedilabtube.client.entities.AcadInstances;
|
||||
import app.fedilab.fedilabtube.client.entities.Error;
|
||||
import app.fedilab.fedilabtube.client.entities.Oauth;
|
||||
import app.fedilab.fedilabtube.client.entities.OauthParams;
|
||||
import app.fedilab.fedilabtube.client.entities.Token;
|
||||
import app.fedilab.fedilabtube.client.entities.WellKnownNodeinfo;
|
||||
import app.fedilab.fedilabtube.client.mastodon.RetrofitMastodonAPI;
|
||||
import app.fedilab.fedilabtube.databinding.ActivityLoginBinding;
|
||||
import app.fedilab.fedilabtube.helper.Helper;
|
||||
import app.fedilab.fedilabtube.helper.HelperAcadInstance;
|
||||
@ -120,16 +120,7 @@ public class LoginActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
if (!BuildConfig.full_instances) {
|
||||
binding.loginUid.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
if (binding.loginUid.getText() != null && android.util.Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) {
|
||||
String[] emailArray = binding.loginUid.getText().toString().split("@");
|
||||
if (emailArray.length > 1) {
|
||||
binding.loginButton.callOnClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
binding.loginUidContainer.setVisibility(View.GONE);
|
||||
binding.loginPasswdContainer.setVisibility(View.GONE);
|
||||
binding.loginInstanceContainer.setVisibility(View.GONE);
|
||||
@ -137,24 +128,24 @@ public class LoginActivity extends AppCompatActivity {
|
||||
binding.instancePickerTitle.setVisibility(View.VISIBLE);
|
||||
binding.instancePicker.setVisibility(View.VISIBLE);
|
||||
|
||||
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
|
||||
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator();
|
||||
String[] academiesKey = new String[HelperAcadInstance.instances_themes.size()];
|
||||
String[] academiesValue = new String[HelperAcadInstance.instances_themes.size()];
|
||||
|
||||
List<AcadInstances> acadInstances = AcadInstances.getInstances();
|
||||
String[] academiesKey = new String[acadInstances.size()];
|
||||
String[] academiesValue = new String[acadInstances.size()];
|
||||
String acad = HelperInstance.getLiveInstance(LoginActivity.this);
|
||||
int position = 0;
|
||||
int i = 0;
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, String> pair = it.next();
|
||||
academiesKey[i] = pair.getKey();
|
||||
academiesValue[i] = pair.getValue();
|
||||
if (pair.getValue().compareTo(acad) == 0) {
|
||||
for (AcadInstances ac : acadInstances) {
|
||||
academiesKey[i] = ac.getName();
|
||||
academiesValue[i] = ac.getUrl();
|
||||
Log.v(Helper.TAG, "url 1: " + ac.getUrl());
|
||||
Log.v(Helper.TAG, "url 2: " + acad);
|
||||
if (ac.getUrl().compareTo(acad) == 0) {
|
||||
position = i;
|
||||
Log.v(Helper.TAG, "ok: " + position);
|
||||
}
|
||||
it.remove();
|
||||
i++;
|
||||
}
|
||||
binding.instancePicker.setSelection(position, true);
|
||||
ArrayAdapter<String> adapterChannel = new ArrayAdapter<>(LoginActivity.this,
|
||||
android.R.layout.simple_spinner_dropdown_item, academiesKey);
|
||||
binding.instancePicker.setAdapter(adapterChannel);
|
||||
@ -162,6 +153,17 @@ public class LoginActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
acadInstance = academiesValue[position];
|
||||
if (acadInstances.get(position).isOpenId()) {
|
||||
binding.loginUidContainer.setVisibility(View.GONE);
|
||||
binding.loginPasswdContainer.setVisibility(View.GONE);
|
||||
binding.loginInstanceContainer.setVisibility(View.GONE);
|
||||
binding.createAnAccountPeertube.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.loginUidContainer.setVisibility(View.VISIBLE);
|
||||
binding.loginPasswdContainer.setVisibility(View.VISIBLE);
|
||||
binding.loginInstanceContainer.setVisibility(View.GONE);
|
||||
binding.createAnAccountPeertube.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -169,13 +171,17 @@ public class LoginActivity extends AppCompatActivity {
|
||||
|
||||
}
|
||||
});
|
||||
binding.instancePicker.setSelection(position, true);
|
||||
}
|
||||
if (BuildConfig.allow_remote_connections) {
|
||||
binding.loginInstance.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
if (binding.loginInstance.getText() != null) {
|
||||
new Thread(() -> {
|
||||
String testInstance = binding.loginInstance.getText().toString();
|
||||
String testInstance = binding.loginInstance.getText().toString().trim();
|
||||
if (testInstance.length() == 0) {
|
||||
return;
|
||||
}
|
||||
WellKnownNodeinfo.NodeInfo instanceNodeInfo = null;
|
||||
if (BuildConfig.allow_remote_connections) {
|
||||
instanceNodeInfo = new RetrofitPeertubeAPI(LoginActivity.this, testInstance, null).getNodeInfo();
|
||||
@ -193,7 +199,7 @@ public class LoginActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
binding.loginButton.setOnClickListener(v -> {
|
||||
if (!BuildConfig.full_instances) {
|
||||
if (!BuildConfig.full_instances && AcadInstances.isOpenId(acadInstance)) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, acadInstance, null).oauthClient(null, null, null, null);
|
||||
@ -272,7 +278,6 @@ public class LoginActivity extends AppCompatActivity {
|
||||
return;
|
||||
}
|
||||
String finalInstance = instance;
|
||||
if (BuildConfig.full_instances) {
|
||||
new Thread(() -> {
|
||||
WellKnownNodeinfo.NodeInfo instanceNodeInfo = null;
|
||||
if (BuildConfig.allow_remote_connections) {
|
||||
@ -281,7 +286,6 @@ public class LoginActivity extends AppCompatActivity {
|
||||
connectToFediverse(finalInstance, instanceNodeInfo);
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,33 +16,30 @@ package app.fedilab.fedilabtube;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
@ -51,18 +48,9 @@ import com.kobakei.ratethisapp.RateThisApp;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Matcher;
|
||||
@ -71,7 +59,7 @@ import java.util.regex.Pattern;
|
||||
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
|
||||
import app.fedilab.fedilabtube.client.data.AccountData.Account;
|
||||
import app.fedilab.fedilabtube.client.data.InstanceData;
|
||||
import app.fedilab.fedilabtube.client.data.VideoData;
|
||||
import app.fedilab.fedilabtube.client.entities.AcadInstances;
|
||||
import app.fedilab.fedilabtube.client.entities.Error;
|
||||
import app.fedilab.fedilabtube.client.entities.OauthParams;
|
||||
import app.fedilab.fedilabtube.client.entities.PeertubeInformation;
|
||||
@ -93,18 +81,13 @@ import app.fedilab.fedilabtube.sqlite.Sqlite;
|
||||
import app.fedilab.fedilabtube.sqlite.StoredInstanceDAO;
|
||||
import app.fedilab.fedilabtube.viewmodel.TimelineVM;
|
||||
import es.dmoral.toasty.Toasty;
|
||||
import su.litvak.chromecast.api.v2.ChromeCast;
|
||||
import su.litvak.chromecast.api.v2.ChromeCasts;
|
||||
import su.litvak.chromecast.api.v2.ChromeCastsListener;
|
||||
import su.litvak.chromecast.api.v2.MediaStatus;
|
||||
|
||||
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.NORMAL;
|
||||
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.SURFING;
|
||||
import static app.fedilab.fedilabtube.helper.Helper.isLoggedInType;
|
||||
import static app.fedilab.fedilabtube.helper.Helper.peertubeInformation;
|
||||
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements ChromeCastsListener {
|
||||
public class MainActivity extends BaseMainActivity {
|
||||
|
||||
|
||||
public static int PICK_INSTANCE = 5641;
|
||||
@ -112,12 +95,12 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
public static UserMe userMe;
|
||||
public static InstanceData.InstanceConfig instanceConfig;
|
||||
public static TypeOfConnection typeOfConnection;
|
||||
public static List<ChromeCast> chromeCasts;
|
||||
public static ChromeCast chromeCast;
|
||||
public static boolean chromecastActivated = false;
|
||||
|
||||
|
||||
private DisplayVideosFragment recentFragment, locaFragment, trendingFragment, subscriptionFragment, mostLikedFragment;
|
||||
private DisplayOverviewFragment overviewFragment;
|
||||
private ActivityMainBinding binding;
|
||||
public static int badgeCount;
|
||||
private final BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
|
||||
= item -> {
|
||||
int itemId = item.getItemId();
|
||||
@ -156,8 +139,7 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
}
|
||||
return true;
|
||||
};
|
||||
private BroadcastReceiver manage_chromecast;
|
||||
private VideoData.Video castedTube;
|
||||
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
public static void showRadioButtonDialogFullInstances(Activity activity, boolean storeInDb) {
|
||||
@ -232,129 +214,26 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newChromeCastDiscovered(ChromeCast chromeCast) {
|
||||
if (chromeCasts == null) {
|
||||
chromeCasts = new ArrayList<>();
|
||||
chromeCasts.add(chromeCast);
|
||||
} else {
|
||||
boolean canBeAdded = true;
|
||||
for (ChromeCast cast : chromeCasts) {
|
||||
if (cast.getName().compareTo(chromeCast.getName()) == 0) {
|
||||
canBeAdded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canBeAdded) {
|
||||
chromeCasts.add(chromeCast);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (chromeCast.isAppRunning(Helper.CAST_ID) && chromeCast.getMediaStatus() != null && chromeCast.getMediaStatus().playerState != null) {
|
||||
if (binding.castInfo.getVisibility() == View.GONE) {
|
||||
binding.castInfo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void chromeCastRemoved(ChromeCast chromeCast) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
ChromeCasts.unregisterListener(this);
|
||||
if (manage_chromecast != null) {
|
||||
LocalBroadcastManager.getInstance(MainActivity.this).unregisterReceiver(manage_chromecast);
|
||||
|
||||
new Thread(() -> {
|
||||
if (chromeCasts != null && chromeCasts.size() > 0) {
|
||||
for (ChromeCast cast : chromeCasts) {
|
||||
try {
|
||||
cast.stopApp();
|
||||
cast.disconnect();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (chromeCasts != null) {
|
||||
chromeCasts = null;
|
||||
}
|
||||
if (chromeCast != null) {
|
||||
chromeCast = null;
|
||||
}
|
||||
}
|
||||
|
||||
//Method for discovering cast devices
|
||||
public void discoverCast() {
|
||||
new Thread(() -> {
|
||||
if (chromeCasts != null) {
|
||||
for (ChromeCast cast : chromeCasts) {
|
||||
try {
|
||||
cast.disconnect();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
chromeCasts = null;
|
||||
}
|
||||
chromeCasts = new ArrayList<>();
|
||||
try {
|
||||
List<NetworkInterface> interfaces;
|
||||
interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface ni : interfaces) {
|
||||
if ((!ni.isLoopback()) && ni.isUp() && (ni.getName().equals("wlan0"))) {
|
||||
Enumeration<InetAddress> inetAddressEnumeration = ni.getInetAddresses();
|
||||
while (inetAddressEnumeration.hasMoreElements()) {
|
||||
InetAddress inetAddress = inetAddressEnumeration.nextElement();
|
||||
ChromeCasts.restartDiscovery(inetAddress);
|
||||
int tryFind = 0;
|
||||
while (ChromeCasts.get().isEmpty() && tryFind < 5) {
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(1000);
|
||||
tryFind++;
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ChromeCasts.stopDiscovery();
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = this::invalidateOptionsMenu;
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
View view = binding.getRoot();
|
||||
setContentView(view);
|
||||
ChromeCastsListener chromeCastsListener = this;
|
||||
ChromeCasts.registerListener(chromeCastsListener);
|
||||
binding = super.binding;
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
typeOfConnection = TypeOfConnection.UNKNOWN;
|
||||
|
||||
badgeCount = 0;
|
||||
|
||||
binding.navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
|
||||
|
||||
@ -392,6 +271,11 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
if (!Helper.isLoggedIn(MainActivity.this)) {
|
||||
PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
|
||||
binding.viewpager.setAdapter(mPagerAdapter);
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
badgeCount = new RetrofitPeertubeAPI(MainActivity.this).unreadNotifications();
|
||||
invalidateOptionsMenu();
|
||||
}).start();
|
||||
}
|
||||
binding.viewpager.setOffscreenPageLimit(5);
|
||||
|
||||
@ -454,92 +338,11 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
PlaylistExportHelper.manageIntentUrl(MainActivity.this, getIntent());
|
||||
}
|
||||
|
||||
binding.castClose.setOnClickListener(v -> {
|
||||
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
|
||||
Bundle b = new Bundle();
|
||||
b.putInt("displayed", 0);
|
||||
intentBC.putExtras(b);
|
||||
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intentBC);
|
||||
});
|
||||
|
||||
binding.castTogglePlay.setOnClickListener(v -> {
|
||||
if (chromeCast != null) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> binding.castTogglePlay.setVisibility(View.GONE);
|
||||
mainHandler.post(myRunnable);
|
||||
int icon = -1;
|
||||
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
|
||||
chromeCast.pause();
|
||||
icon = R.drawable.ic_baseline_play_arrow_32;
|
||||
} else if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
|
||||
chromeCast.play();
|
||||
icon = R.drawable.ic_baseline_pause_32;
|
||||
}
|
||||
if (icon != -1) {
|
||||
int finalIcon = icon;
|
||||
myRunnable = () -> binding.castTogglePlay.setImageResource(finalIcon);
|
||||
mainHandler.post(myRunnable);
|
||||
}
|
||||
myRunnable = () -> binding.castTogglePlay.setVisibility(View.VISIBLE);
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
manage_chromecast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Bundle b = intent.getExtras();
|
||||
assert b != null;
|
||||
int state = b.getInt("state_asked", -1);
|
||||
int displayed = b.getInt("displayed", -1);
|
||||
castedTube = b.getParcelable("castedTube");
|
||||
|
||||
if (state == 1) {
|
||||
discoverCast();
|
||||
} else if (state == 0) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (chromeCast != null) {
|
||||
chromeCast.stopApp();
|
||||
chromeCast.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (displayed == 1) {
|
||||
chromecastActivated = true;
|
||||
if (castedTube != null) {
|
||||
binding.castInfo.setVisibility(View.VISIBLE);
|
||||
Helper.loadGiF(MainActivity.this, castedTube.getThumbnailPath(), binding.castView);
|
||||
binding.castTitle.setText(castedTube.getTitle());
|
||||
binding.castDescription.setText(castedTube.getDescription());
|
||||
}
|
||||
} else if (displayed == 0) {
|
||||
chromecastActivated = false;
|
||||
binding.castInfo.setVisibility(View.GONE);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (chromeCast != null) {
|
||||
chromeCast.stopApp();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
};
|
||||
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
|
||||
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(manage_chromecast, new IntentFilter(Helper.RECEIVE_CAST_SETTINGS));
|
||||
int search_cast = sharedpreferences.getInt(getString(R.string.set_cast_choice), 0);
|
||||
|
||||
int search_cast = sharedpreferences.getInt(getString(R.string.set_cast_choice), BuildConfig.cast_enabled);
|
||||
if (search_cast == 1) {
|
||||
discoverCast();
|
||||
}
|
||||
@ -558,6 +361,12 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void refreshToken() {
|
||||
new Thread(() -> {
|
||||
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
@ -685,24 +494,31 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
MenuItem settingsItem = menu.findItem(R.id.action_settings);
|
||||
MenuItem sepiaSearchItem = menu.findItem(R.id.action_sepia_search);
|
||||
MenuItem incognitoItem = menu.findItem(R.id.action_incognito);
|
||||
MenuItem instanceItem = menu.findItem(R.id.action_change_instance);
|
||||
MenuItem accountItem = menu.findItem(R.id.action_account);
|
||||
MenuItem donateItem = menu.findItem(R.id.action_donate);
|
||||
MenuItem changeInstanceItem = menu.findItem(R.id.action_change_instance);
|
||||
|
||||
FrameLayout rootView = (FrameLayout) accountItem.getActionView();
|
||||
|
||||
if (BuildConfig.surfing_mode && ((Helper.isLoggedIn(MainActivity.this) && typeOfConnection == NORMAL) || typeOfConnection == SURFING)) {
|
||||
binding.instances.setVisibility(View.VISIBLE);
|
||||
binding.instances.setOnClickListener(null);
|
||||
binding.instances.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(MainActivity.this, ManageInstancesActivity.class);
|
||||
startActivity(intent);
|
||||
overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up);
|
||||
});
|
||||
FrameLayout redCircle = rootView.findViewById(R.id.view_alert_red_circle);
|
||||
TextView countTextView = rootView.findViewById(R.id.view_alert_count_textview);
|
||||
//change counter for notifications
|
||||
if (badgeCount > 0) {
|
||||
countTextView.setText(String.valueOf(badgeCount));
|
||||
redCircle.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.instances.setVisibility(View.GONE);
|
||||
redCircle.setVisibility(View.GONE);
|
||||
}
|
||||
TooltipCompat.setTooltipText(accountItem.getActionView(), getText(R.string.account));
|
||||
if (BuildConfig.FLAVOR.compareTo("google_full") == 0) {
|
||||
donateItem.setVisible(true);
|
||||
}
|
||||
|
||||
if (!BuildConfig.instance_switcher) {
|
||||
changeInstanceItem.setVisible(false);
|
||||
}
|
||||
switch (typeOfConnection) {
|
||||
case UNKNOWN:
|
||||
instanceItem.setVisible(false);
|
||||
accountItem.setVisible(false);
|
||||
uploadItem.setVisible(false);
|
||||
myVideosItem.setVisible(false);
|
||||
@ -716,7 +532,9 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
case NORMAL:
|
||||
accountItem.setVisible(true);
|
||||
if (Helper.isLoggedIn(MainActivity.this)) {
|
||||
instanceItem.setVisible(false);
|
||||
if (!BuildConfig.full_instances) {
|
||||
changeInstanceItem.setVisible(false);
|
||||
}
|
||||
uploadItem.setVisible(true);
|
||||
myVideosItem.setVisible(true);
|
||||
playslistItem.setVisible(true);
|
||||
@ -728,7 +546,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
boolean checked = sharedpreferences.getBoolean(getString(R.string.set_store_in_history), true);
|
||||
incognitoItem.setChecked(checked);
|
||||
} else {
|
||||
instanceItem.setVisible(true);
|
||||
uploadItem.setVisible(false);
|
||||
myVideosItem.setVisible(false);
|
||||
playslistItem.setVisible(!BuildConfig.full_instances);
|
||||
@ -739,7 +556,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
}
|
||||
break;
|
||||
case SURFING:
|
||||
instanceItem.setVisible(false);
|
||||
accountItem.setVisible(true);
|
||||
uploadItem.setVisible(false);
|
||||
myVideosItem.setVisible(false);
|
||||
@ -751,9 +567,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
break;
|
||||
}
|
||||
|
||||
if (!BuildConfig.instance_switcher) {
|
||||
instanceItem.setVisible(false);
|
||||
}
|
||||
|
||||
if (!BuildConfig.sepia_search) {
|
||||
sepiaSearchItem.setVisible(false);
|
||||
@ -782,13 +595,24 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
final MenuItem accountItem = menu.findItem(R.id.action_account);
|
||||
FrameLayout rootView = (FrameLayout) accountItem.getActionView();
|
||||
rootView.setOnClickListener(v -> onOptionsItemSelected(accountItem));
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
String type = null;
|
||||
String action = "TIMELINE";
|
||||
if (item.getItemId() == R.id.action_change_instance) {
|
||||
if (BuildConfig.full_instances) {
|
||||
showRadioButtonDialogFullInstances(MainActivity.this, false);
|
||||
Intent intent = new Intent(MainActivity.this, ManageInstancesActivity.class);
|
||||
startActivity(intent);
|
||||
overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up);
|
||||
} else {
|
||||
showRadioButtonDialog();
|
||||
}
|
||||
@ -804,11 +628,14 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
} else {
|
||||
if (Helper.canMakeAction(MainActivity.this)) {
|
||||
intent = new Intent(MainActivity.this, AccountActivity.class);
|
||||
startActivity(intent);
|
||||
overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up);
|
||||
} else {
|
||||
intent = new Intent(MainActivity.this, LoginActivity.class);
|
||||
}
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
} else if (item.getItemId() == R.id.action_upload) {
|
||||
Intent intent = new Intent(MainActivity.this, PeertubeUploadActivity.class);
|
||||
startActivity(intent);
|
||||
@ -847,6 +674,9 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
} else if (item.getItemId() == R.id.action_about) {
|
||||
Intent intent = new Intent(MainActivity.this, AboutActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (item.getItemId() == R.id.action_donate) {
|
||||
Intent intent = new Intent(MainActivity.this, DonationActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (item.getItemId() == R.id.action_incognito) {
|
||||
item.setChecked(!item.isChecked());
|
||||
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
@ -896,21 +726,19 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
|
||||
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
String acad = HelperInstance.getLiveInstance(MainActivity.this);
|
||||
int i = 0;
|
||||
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
|
||||
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator();
|
||||
String[] academiesKey = new String[HelperAcadInstance.instances_themes.size()];
|
||||
String[] academiesValue = new String[HelperAcadInstance.instances_themes.size()];
|
||||
List<AcadInstances> acadInstances = AcadInstances.getInstances();
|
||||
String[] academiesKey = new String[acadInstances.size()];
|
||||
String[] academiesValue = new String[acadInstances.size()];
|
||||
int position = 0;
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, String> pair = it.next();
|
||||
academiesKey[i] = pair.getKey();
|
||||
academiesValue[i] = pair.getValue();
|
||||
if (pair.getValue().compareTo(acad) == 0) {
|
||||
for (AcadInstances ac : acadInstances) {
|
||||
academiesKey[i] = ac.getName();
|
||||
academiesValue[i] = ac.getUrl();
|
||||
if (ac.getUrl().compareTo(acad) == 0) {
|
||||
position = i;
|
||||
}
|
||||
it.remove();
|
||||
i++;
|
||||
}
|
||||
|
||||
alt_bld.setSingleChoiceItems(academiesKey, position, (dialog, item) -> {
|
||||
String newInstance = academiesValue[item];
|
||||
SharedPreferences.Editor editor = sharedpreferences.edit();
|
||||
|
@ -134,6 +134,9 @@ public class MastodonWebviewConnectActivity extends AppCompatActivity {
|
||||
return false;
|
||||
}
|
||||
String code = val[1];
|
||||
if (code.contains("&")) {
|
||||
code = code.split("&")[0];
|
||||
}
|
||||
OauthParams oauthParams = new OauthParams();
|
||||
oauthParams.setClient_id(clientId);
|
||||
oauthParams.setClient_secret(clientSecret);
|
||||
|
@ -44,7 +44,6 @@ import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@ -52,7 +51,6 @@ import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
@ -63,14 +61,12 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
@ -111,8 +107,6 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -163,19 +157,12 @@ import app.fedilab.fedilabtube.webview.CustomWebview;
|
||||
import app.fedilab.fedilabtube.webview.MastalabWebChromeClient;
|
||||
import app.fedilab.fedilabtube.webview.MastalabWebViewClient;
|
||||
import es.dmoral.toasty.Toasty;
|
||||
import su.litvak.chromecast.api.v2.ChromeCast;
|
||||
import su.litvak.chromecast.api.v2.MediaStatus;
|
||||
import su.litvak.chromecast.api.v2.Status;
|
||||
|
||||
import static app.fedilab.fedilabtube.MainActivity.chromeCast;
|
||||
import static app.fedilab.fedilabtube.MainActivity.chromeCasts;
|
||||
import static app.fedilab.fedilabtube.MainActivity.chromecastActivated;
|
||||
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.ADD_COMMENT;
|
||||
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.RATEVIDEO;
|
||||
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPLY;
|
||||
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT;
|
||||
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPORT_VIDEO;
|
||||
import static app.fedilab.fedilabtube.helper.Helper.CAST_ID;
|
||||
import static app.fedilab.fedilabtube.helper.Helper.canMakeAction;
|
||||
import static app.fedilab.fedilabtube.helper.Helper.getAttColor;
|
||||
import static app.fedilab.fedilabtube.helper.Helper.isLoggedIn;
|
||||
@ -184,15 +171,13 @@ import static app.fedilab.fedilabtube.helper.Helper.peertubeInformation;
|
||||
import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO;
|
||||
|
||||
|
||||
public class PeertubeActivity extends AppCompatActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, TorrentListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction {
|
||||
public class PeertubeActivity extends BasePeertubeActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, TorrentListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction {
|
||||
|
||||
public static String video_id;
|
||||
public static List<String> playedVideos = new ArrayList<>();
|
||||
private String peertubeInstance, videoUuid;
|
||||
private ImageView fullScreenIcon;
|
||||
private SimpleExoPlayer player;
|
||||
private boolean fullScreenMode;
|
||||
private VideoData.Video peertube;
|
||||
private int mode;
|
||||
private Map<String, List<PlaylistExist>> playlists;
|
||||
private boolean playInMinimized, autoPlay, autoFullscreen;
|
||||
@ -218,8 +203,10 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
private String currentCaption;
|
||||
private boolean isRemote;
|
||||
private boolean willPlayFromIntent;
|
||||
private String chromeCastVideoURL;
|
||||
|
||||
private app.fedilab.fedilabtube.client.mastodon.Status status;
|
||||
Uri captionURI;
|
||||
String captionLang;
|
||||
|
||||
public static void hideKeyboard(Activity activity) {
|
||||
if (activity != null && activity.getWindow() != null) {
|
||||
@ -305,10 +292,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
|
||||
View view = binding.getRoot();
|
||||
setContentView(view);
|
||||
binding = super.binding;
|
||||
videoOrientationType = videoOrientation.LANDSCAPE;
|
||||
max_id = "0";
|
||||
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
|
||||
@ -510,34 +494,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
}
|
||||
});
|
||||
|
||||
binding.castPlay.setOnClickListener(v -> {
|
||||
binding.castLoader.setVisibility(View.VISIBLE);
|
||||
if (chromeCast != null) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
int icon = -1;
|
||||
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
|
||||
chromeCast.pause();
|
||||
icon = R.drawable.ic_baseline_play_arrow_32;
|
||||
} else if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
|
||||
chromeCast.play();
|
||||
icon = R.drawable.ic_baseline_pause_32;
|
||||
}
|
||||
if (icon != -1) {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
int finalIcon = icon;
|
||||
Runnable myRunnable = () -> binding.castPlay.setImageResource(finalIcon);
|
||||
mainHandler.post(myRunnable);
|
||||
}
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> binding.castLoader.setVisibility(View.GONE);
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -664,7 +621,8 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
secInt = Integer.parseInt(sec.replace("s", ""));
|
||||
totalSeconds += secInt;
|
||||
}
|
||||
|
||||
captionURI = null;
|
||||
captionLang = null;
|
||||
if (instance != null && uuid != null) {
|
||||
peertubeInstance = instance.replace("https://", "").replace("http://", "");
|
||||
sepiaSearch = true; // Sepia search flag is used because, at this time we don't know if the video is federated.
|
||||
@ -795,20 +753,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.video_menu, menu);
|
||||
MenuItem castItem = menu.findItem(R.id.action_cast);
|
||||
if (chromeCasts != null && chromeCasts.size() > 0) {
|
||||
castItem.setVisible(true);
|
||||
if (chromeCast != null && chromeCast.isConnected()) {
|
||||
castItem.setIcon(R.drawable.ic_baseline_cast_connected_24);
|
||||
} else {
|
||||
castItem.setIcon(R.drawable.ic_baseline_cast_24);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
@ -818,101 +762,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
}
|
||||
finish();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_cast) {
|
||||
if (chromeCasts != null && chromeCasts.size() > 0) {
|
||||
String[] chromecast_choice = new String[chromeCasts.size()];
|
||||
AlertDialog.Builder alt_bld = new AlertDialog.Builder(this);
|
||||
alt_bld.setTitle(R.string.chromecast_choice);
|
||||
int i = 0;
|
||||
for (ChromeCast cc : chromeCasts) {
|
||||
chromecast_choice[i] = cc.getTitle();
|
||||
i++;
|
||||
}
|
||||
i = 0;
|
||||
for (ChromeCast cc : chromeCasts) {
|
||||
if (chromecastActivated && cc.isConnected()) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
alt_bld.setSingleChoiceItems(chromecast_choice, i, (dialog, position) -> {
|
||||
chromeCast = chromeCasts.get(position);
|
||||
new Thread(() -> {
|
||||
if (chromeCast != null) {
|
||||
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
|
||||
Bundle b = new Bundle();
|
||||
if (chromecastActivated) {
|
||||
b.putInt("displayed", 0);
|
||||
intentBC.putExtras(b);
|
||||
LocalBroadcastManager.getInstance(PeertubeActivity.this).sendBroadcast(intentBC);
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> {
|
||||
binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
|
||||
binding.castController.setVisibility(View.GONE);
|
||||
};
|
||||
mainHandler.post(myRunnable);
|
||||
|
||||
} else {
|
||||
b.putInt("displayed", 1);
|
||||
b.putParcelable("castedTube", peertube);
|
||||
intentBC.putExtras(b);
|
||||
LocalBroadcastManager.getInstance(PeertubeActivity.this).sendBroadcast(intentBC);
|
||||
try {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> {
|
||||
invalidateOptionsMenu();
|
||||
binding.castLoader.setVisibility(View.VISIBLE);
|
||||
player.setPlayWhenReady(false);
|
||||
binding.doubleTapPlayerView.setVisibility(View.GONE);
|
||||
binding.castController.setVisibility(View.VISIBLE);
|
||||
dialog.dismiss();
|
||||
if (chromeCastVideoURL != null) {
|
||||
if (player != null && player.getCurrentPosition() > 0) {
|
||||
chromeCastVideoURL += "?start=" + (player.getCurrentPosition() / 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
mainHandler.post(myRunnable);
|
||||
if (!chromeCast.isConnected()) {
|
||||
chromeCast.connect();
|
||||
}
|
||||
myRunnable = this::invalidateOptionsMenu;
|
||||
mainHandler.post(myRunnable);
|
||||
Status status = chromeCast.getStatus();
|
||||
if (chromeCast.isAppAvailable(CAST_ID) && !status.isAppRunning(CAST_ID)) {
|
||||
chromeCast.launchApp(CAST_ID);
|
||||
}
|
||||
|
||||
if (chromeCastVideoURL != null) {
|
||||
String mime = MimeTypeMap.getFileExtensionFromUrl(chromeCastVideoURL);
|
||||
chromeCast.setRequestTimeout(60000);
|
||||
chromeCast.load(peertube.getTitle(), null, chromeCastVideoURL, mime);
|
||||
chromeCast.play();
|
||||
binding.castPlay.setImageResource(R.drawable.ic_baseline_pause_32);
|
||||
}
|
||||
myRunnable = () -> binding.castLoader.setVisibility(View.GONE);
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> {
|
||||
invalidateOptionsMenu();
|
||||
dialog.dismiss();
|
||||
};
|
||||
mainHandler.post(myRunnable);
|
||||
|
||||
}
|
||||
}).start();
|
||||
|
||||
});
|
||||
alt_bld.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
AlertDialog alert = alt_bld.create();
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@ -1018,7 +867,8 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
secInt = Integer.parseInt(sec.replace("strue", ""));
|
||||
totalSeconds += secInt;
|
||||
}
|
||||
|
||||
captionURI = null;
|
||||
captionLang = null;
|
||||
if (instance != null && uuid != null) {
|
||||
peertubeInstance = instance.replace("https://", "").replace("http://", "");
|
||||
sepiaSearch = true; // Sepia search flag is used because, at this time we don't know if the video is federated.
|
||||
@ -1069,6 +919,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller);
|
||||
DefaultTimeBar exo_progress = controlView.findViewById(R.id.exo_progress);
|
||||
TextView exo_duration = controlView.findViewById(R.id.exo_duration);
|
||||
TextView exo_position = controlView.findViewById(R.id.exo_position);
|
||||
TextView exo_live_badge = controlView.findViewById(R.id.exo_live_badge);
|
||||
if (peertube.isLive()) {
|
||||
exo_progress.setVisibility(View.INVISIBLE);
|
||||
@ -1076,9 +927,11 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
exo_live_badge.setVisibility(View.VISIBLE);
|
||||
exo_live_badge.setText(R.string.live);
|
||||
exo_live_badge.setBackgroundResource(R.drawable.rounded_live);
|
||||
exo_position.setVisibility(View.GONE);
|
||||
} else {
|
||||
exo_progress.setVisibility(View.VISIBLE);
|
||||
exo_live_badge.setVisibility(View.GONE);
|
||||
exo_position.setVisibility(View.VISIBLE);
|
||||
exo_duration.setBackground(null);
|
||||
}
|
||||
|
||||
@ -1137,6 +990,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
if (peertube.isLive()) {
|
||||
info_duration.setText(R.string.live);
|
||||
info_duration.setBackgroundResource(R.drawable.rounded_live);
|
||||
info_duration.setBackgroundResource(R.drawable.rounded_live);
|
||||
} else {
|
||||
info_duration.setText(Helper.secondsToString(peertube.getDuration()));
|
||||
}
|
||||
@ -1409,6 +1263,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Manage video to play with different factors
|
||||
*
|
||||
@ -1421,7 +1276,10 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
* @param lang String ("en","fr", etc.)
|
||||
*/
|
||||
private void stream(VideoData.Video video, String localTorrentUrl, String resolution, boolean autoPlay, long position, Uri subtitles, String lang) {
|
||||
String videoURL = localTorrentUrl == null ? video.getFileUrl(resolution, PeertubeActivity.this) : localTorrentUrl;
|
||||
videoURL = localTorrentUrl == null ? video.getFileUrl(resolution, PeertubeActivity.this) : localTorrentUrl;
|
||||
if (subtitles != null) {
|
||||
subtitlesStr = subtitles.toString();
|
||||
}
|
||||
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
|
||||
int video_cache = sharedpreferences.getInt(Helper.SET_VIDEO_CACHE, Helper.DEFAULT_VIDEO_CACHE_MB);
|
||||
if (videoURL != null && (videoURL.endsWith(".torrent") || videoURL.startsWith("magnet"))) {
|
||||
@ -1442,7 +1300,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (video_cache == 0 || dataSourceFactory != null) {
|
||||
if (video_cache == 0 || dataSourceFactory != null || video.isLive()) {
|
||||
if (dataSourceFactory == null) {
|
||||
dataSourceFactory = new DefaultDataSourceFactory(PeertubeActivity.this,
|
||||
Util.getUserAgent(PeertubeActivity.this, null), null);
|
||||
@ -1462,8 +1320,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
} else {
|
||||
CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(PeertubeActivity.this);
|
||||
MediaItem mediaItem = new MediaItem.Builder().setUri(videoURL).build();
|
||||
videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
|
||||
.createMediaSource(mediaItem);
|
||||
if (subtitles != null) {
|
||||
MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang, Format.NO_VALUE);
|
||||
subtitleSource = new SingleSampleMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET);
|
||||
@ -1497,6 +1353,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
if (autoPlay) {
|
||||
binding.doubleTapPlayerView.hideController();
|
||||
}
|
||||
// loadCast(video, videoURL, subtitles!=null?subtitles.toString():null);
|
||||
}
|
||||
|
||||
private void fetchComments() {
|
||||
@ -1531,14 +1388,14 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
} else {
|
||||
videoURL = peertube.getFileUrl(resolution, PeertubeActivity.this);
|
||||
}
|
||||
if (peertube != null && peertube.isWaitTranscoding() && peertube.isLive()) {
|
||||
if (peertube != null && peertube.isLive() && videoURL == null) {
|
||||
View parentLayout = findViewById(android.R.id.content);
|
||||
Snackbar snackbar = Snackbar.make(parentLayout, R.string.live_not_started, Snackbar.LENGTH_INDEFINITE);
|
||||
snackbar.setAction(R.string.close, view -> finish());
|
||||
snackbar.show();
|
||||
return;
|
||||
}
|
||||
chromeCastVideoURL = videoURL;
|
||||
|
||||
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
|
||||
String nsfwAction = sharedpreferences.getString(getString(R.string.set_video_sensitive_choice), Helper.BLUR);
|
||||
if (promptNSFW && peertube != null && peertube.isNsfw() && (nsfwAction.compareTo(Helper.BLUR) == 0 || nsfwAction.compareTo(Helper.DO_NOT_LIST) == 0)) {
|
||||
@ -1568,7 +1425,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
@Override
|
||||
public void onConfigurationChanged(@NotNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (binding.castController.getVisibility() == View.VISIBLE) {
|
||||
if (binding.minController.castMiniController.getVisibility() == View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
@ -1621,7 +1478,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
onStopCalled = false;
|
||||
if (player != null && !player.isPlaying()) {
|
||||
if (player != null && !player.isPlaying() && binding.minController.castMiniController.getVisibility() != View.VISIBLE) {
|
||||
player.setPlayWhenReady(autoPlay);
|
||||
if (autoPlay) {
|
||||
binding.doubleTapPlayerView.hideController();
|
||||
@ -1637,7 +1494,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
}
|
||||
if (player != null && (!isPlayInMinimized || !playInMinimized)) {
|
||||
player.setPlayWhenReady(false);
|
||||
} else if (playInMinimized && binding.castController.getVisibility() != View.VISIBLE) {
|
||||
} else if (playInMinimized && binding.minController.castMiniController.getVisibility() != View.VISIBLE) {
|
||||
enterVideoMode();
|
||||
}
|
||||
}
|
||||
@ -1652,7 +1509,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String strAction = intent.getAction();
|
||||
if (strAction.equals(Intent.ACTION_SCREEN_OFF)) {
|
||||
if (player != null && isPlayInMinimized) {
|
||||
if (player != null) {
|
||||
if (!sharedpreferences.getBoolean(getString(R.string.set_play_screen_lock_choice), false)) {
|
||||
player.setPlayWhenReady(false);
|
||||
} else {
|
||||
@ -1939,11 +1796,12 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
binding.mediaVideo.player(player);
|
||||
binding.doubleTapPlayerView.setPlayer(player);
|
||||
binding.loader.setVisibility(View.GONE);
|
||||
|
||||
startStream(
|
||||
peertube,
|
||||
null,
|
||||
res,
|
||||
true, position, null, null, false);
|
||||
true, position, captionURI, captionLang, false);
|
||||
}
|
||||
break;
|
||||
case SPEED:
|
||||
@ -1955,7 +1813,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
}
|
||||
break;
|
||||
case CAPTION:
|
||||
Uri uri = null;
|
||||
Caption captionToUse = null;
|
||||
for (Caption caption : captions) {
|
||||
if (caption.getLanguage().getId().compareTo(item.getStrId()) == 0) {
|
||||
@ -1965,10 +1822,12 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
}
|
||||
if (captionToUse != null) {
|
||||
if (!sepiaSearch) {
|
||||
uri = Uri.parse("https://" + HelperInstance.getLiveInstance(PeertubeActivity.this) + captionToUse.getCaptionPath());
|
||||
captionURI = Uri.parse("https://" + HelperInstance.getLiveInstance(PeertubeActivity.this) + captionToUse.getCaptionPath());
|
||||
} else {
|
||||
uri = Uri.parse("https://" + peertubeInstance + captionToUse.getCaptionPath());
|
||||
captionURI = Uri.parse("https://" + peertubeInstance + captionToUse.getCaptionPath());
|
||||
}
|
||||
} else {
|
||||
captionURI = null;
|
||||
}
|
||||
currentCaption = item.getStrId();
|
||||
long newPosition = player.getCurrentPosition();
|
||||
@ -1980,14 +1839,15 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
player = new SimpleExoPlayer.Builder(PeertubeActivity.this).setTrackSelector(trackSelector).build();
|
||||
binding.mediaVideo.player(player);
|
||||
binding.doubleTapPlayerView.setPlayer(player);
|
||||
captionLang = item.getStrId();
|
||||
startStream(
|
||||
peertube,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
newPosition,
|
||||
uri,
|
||||
item.getStrId(),
|
||||
captionURI,
|
||||
captionLang,
|
||||
false
|
||||
);
|
||||
break;
|
||||
@ -2360,16 +2220,16 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
|
||||
DrawableCompat.setTint(bookmark, color);
|
||||
}
|
||||
|
||||
if (status.isReblogged()) {
|
||||
if (reblog != null && status.isReblogged()) {
|
||||
reblog.setColorFilter(getResources().getColor(R.color.positive_thumbs), PorterDuff.Mode.SRC_ATOP);
|
||||
DrawableCompat.setTint(reblog, getResources().getColor(R.color.positive_thumbs));
|
||||
}
|
||||
if (status.isFavourited()) {
|
||||
if (favorite != null && status.isFavourited()) {
|
||||
favorite.setColorFilter(getResources().getColor(R.color.favorite), PorterDuff.Mode.SRC_ATOP);
|
||||
DrawableCompat.setTint(favorite, getResources().getColor(R.color.favorite));
|
||||
}
|
||||
|
||||
if (status.isBookmarked()) {
|
||||
if (bookmark != null && status.isBookmarked()) {
|
||||
bookmark.setColorFilter(getResources().getColor(R.color.bookmark), PorterDuff.Mode.SRC_ATOP);
|
||||
DrawableCompat.setTint(bookmark, getResources().getColor(R.color.bookmark));
|
||||
}
|
||||
|
@ -284,6 +284,16 @@ public interface PeertubeService {
|
||||
@GET("users/me/notifications")
|
||||
Call<NotificationData> getNotifications(@Header("Authorization") String credentials, @Query("start") String maxId, @Query("count") String count, @Query("since_id") String sinceId);
|
||||
|
||||
@GET("users/me/notifications?start=0&count=0&unread=true")
|
||||
Call<NotificationData> countNotifications(@Header("Authorization") String credentials);
|
||||
|
||||
@POST("users/me/notifications/read-all")
|
||||
Call<String> markAllAsRead(@Header("Authorization") String credentials);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("users/me/notifications/read")
|
||||
Call<String> markAsRead(@Header("Authorization") String credentials, @Field("ids[]") List<String> ids);
|
||||
|
||||
//Update Notification settings
|
||||
@PUT("users/me/notification-settings")
|
||||
Call<String> updateNotifications(@Header("Authorization") String credentials, @Body NotificationSettings notificationSettings);
|
||||
|
@ -43,6 +43,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import app.fedilab.fedilabtube.BuildConfig;
|
||||
import app.fedilab.fedilabtube.MainActivity;
|
||||
@ -85,6 +86,7 @@ import app.fedilab.fedilabtube.viewmodel.PlaylistsVM;
|
||||
import app.fedilab.fedilabtube.viewmodel.TimelineVM;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
@ -103,6 +105,13 @@ public class RetrofitPeertubeAPI {
|
||||
private String token;
|
||||
private Set<String> selection;
|
||||
|
||||
|
||||
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
|
||||
public RetrofitPeertubeAPI(Context context) {
|
||||
_context = context;
|
||||
instance = HelperInstance.getLiveInstance(context);
|
||||
@ -199,6 +208,7 @@ public class RetrofitPeertubeAPI {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(finalUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(okHttpClient)
|
||||
.build();
|
||||
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
if (token == null) {
|
||||
@ -215,6 +225,7 @@ public class RetrofitPeertubeAPI {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://" + instance)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(okHttpClient)
|
||||
.build();
|
||||
return retrofit.create(PeertubeService.class);
|
||||
}
|
||||
@ -283,6 +294,50 @@ public class RetrofitPeertubeAPI {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve notifications
|
||||
*
|
||||
* @return APIResponse
|
||||
*/
|
||||
public int unreadNotifications() {
|
||||
PeertubeService peertubeService = init();
|
||||
Call<NotificationData> notificationsCall = peertubeService.countNotifications("Bearer " + token);
|
||||
try {
|
||||
Response<NotificationData> response = notificationsCall.execute();
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
return response.body().total;
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all notifications as read
|
||||
*/
|
||||
public void markAllAsRead() {
|
||||
PeertubeService peertubeService = init();
|
||||
Call<String> notificationsCall = peertubeService.markAllAsRead("Bearer " + token);
|
||||
try {
|
||||
Response<String> response = notificationsCall.execute();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a notification as read
|
||||
*/
|
||||
public void markAsRead(String id) {
|
||||
PeertubeService peertubeService = init();
|
||||
ArrayList<String> ids = new ArrayList<>();
|
||||
ids.add(id);
|
||||
Call<String> notificationsCall = peertubeService.markAsRead("Bearer " + token, ids);
|
||||
try {
|
||||
Response<String> response = notificationsCall.execute();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve notifications
|
||||
*
|
||||
@ -1525,7 +1580,10 @@ public class RetrofitPeertubeAPI {
|
||||
}
|
||||
try {
|
||||
RequestBody displayName = RequestBody.create(playlistParams.getDisplayName(), MediaType.parse("text/plain"));
|
||||
RequestBody description = RequestBody.create(playlistParams.getDescription(), MediaType.parse("text/plain"));
|
||||
RequestBody description = null;
|
||||
if (playlistParams.getDescription() != null) {
|
||||
description = RequestBody.create(playlistParams.getDescription(), MediaType.parse("text/plain"));
|
||||
}
|
||||
RequestBody channelId = RequestBody.create(playlistParams.getVideoChannelId(), MediaType.parse("text/plain"));
|
||||
if (apiAction == PlaylistsVM.action.CREATE_PLAYLIST) {
|
||||
Call<VideoPlaylistData.VideoPlaylistCreation> stringCall = peertubeService.addPlaylist(getToken(), displayName, description, playlistParams.getPrivacy(), channelId, bodyThumbnail);
|
||||
|
@ -205,14 +205,19 @@ public class VideoData {
|
||||
}
|
||||
|
||||
public List<File> getAllFile(Context context) {
|
||||
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
int mode = sharedpreferences.getInt(Helper.SET_VIDEO_MODE, Helper.VIDEO_MODE_NORMAL);
|
||||
if (files != null && files.size() > 0) {
|
||||
return files;
|
||||
} else if (streamingPlaylists != null) {
|
||||
List<File> files = new ArrayList<>();
|
||||
for (StreamingPlaylists streamingPlaylists : streamingPlaylists) {
|
||||
if (streamingPlaylists.getFiles().size() > 0) {
|
||||
files.addAll(streamingPlaylists.getFiles());
|
||||
} else {
|
||||
File file = new File();
|
||||
file.setFileUrl(streamingPlaylists.getPlaylistUrl());
|
||||
file.setFileDownloadUrl(streamingPlaylists.getPlaylistUrl());
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
@ -225,24 +230,40 @@ public class VideoData {
|
||||
for (File file : files) {
|
||||
if (file.getResolutions().getLabel().compareTo(resolution) == 0) {
|
||||
if (mode == Helper.VIDEO_MODE_MAGNET) {
|
||||
if (file.getMagnetUri() != null) {
|
||||
return file.getMagnetUri();
|
||||
} else {
|
||||
return file.getFileUrl();
|
||||
}
|
||||
} else if (mode == Helper.VIDEO_MODE_TORRENT) {
|
||||
if (file.getTorrentUrl() != null) {
|
||||
return file.getTorrentUrl();
|
||||
} else {
|
||||
return file.getFileUrl();
|
||||
}
|
||||
} else {
|
||||
return file.getFileUrl();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File file = Helper.defaultFile(context, files);
|
||||
if (file != null) {
|
||||
if (mode == Helper.VIDEO_MODE_MAGNET) {
|
||||
if (file.getMagnetUri() != null) {
|
||||
return file.getMagnetUri();
|
||||
} else {
|
||||
return file.getFileUrl();
|
||||
}
|
||||
} else if (mode == Helper.VIDEO_MODE_TORRENT) {
|
||||
if (file.getTorrentUrl() != null) {
|
||||
return file.getTorrentUrl();
|
||||
} else {
|
||||
return file.getFileUrl();
|
||||
}
|
||||
} else {
|
||||
return file.getFileUrl();
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,109 @@
|
||||
package app.fedilab.fedilabtube.client.entities;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import app.fedilab.fedilabtube.helper.HelperAcadInstance;
|
||||
|
||||
public class AcadInstances {
|
||||
|
||||
private String name;
|
||||
private String url;
|
||||
private boolean openId;
|
||||
|
||||
public static boolean isOpenId(String domain) {
|
||||
List<AcadInstances> instances = getInstances();
|
||||
for (AcadInstances acadInstance : instances) {
|
||||
if (acadInstance.getUrl().compareTo(domain) == 0) {
|
||||
return acadInstance.isOpenId();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<AcadInstances> getInstances() {
|
||||
List<AcadInstances> acadInstances = new ArrayList<>();
|
||||
|
||||
for (String academie : HelperAcadInstance.academies) {
|
||||
AcadInstances acadInstance = new AcadInstances();
|
||||
acadInstance.name = academie;
|
||||
acadInstance.openId = !Arrays.asList(HelperAcadInstance.notOpenId).contains(academie);
|
||||
acadInstance.url = getPeertubeUrl(academie);
|
||||
acadInstances.add(acadInstance);
|
||||
}
|
||||
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
|
||||
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, String> pair = it.next();
|
||||
AcadInstances acadInstance = new AcadInstances();
|
||||
acadInstance.name = pair.getKey();
|
||||
acadInstance.openId = true;
|
||||
acadInstance.url = pair.getValue();
|
||||
acadInstances.add(acadInstance);
|
||||
it.remove();
|
||||
}
|
||||
return acadInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the peertube URL depending of the academic domain name
|
||||
*
|
||||
* @param acad String academic domain name
|
||||
* @return String the peertube URL
|
||||
*/
|
||||
private static String getPeertubeUrl(String acad) {
|
||||
|
||||
if (acad.compareTo("education.gouv.fr") == 0 || acad.compareTo("igesr.gouv.fr") == 0) {
|
||||
acad = "education.fr";
|
||||
} else if (acad.compareTo("ac-nancy-metz.fr") == 0) {
|
||||
acad = "ac-nancy.fr";
|
||||
} else if (acad.compareTo("clermont.fr") == 0) {
|
||||
acad = "clermont-ferrand.fr";
|
||||
} else if (acad.compareTo("ac-wf.wf") == 0 || acad.compareTo("ac-mayotte.fr") == 0 || acad.compareTo("ac-noumea.nc") == 0
|
||||
|| acad.compareTo("ac-guadeloupe.fr") == 0 || acad.compareTo("monvr.pf") == 0 || acad.compareTo("ac-reunion.fr") == 0 ||
|
||||
acad.compareTo("ac-martinique.fr") == 0 || acad.compareTo("ac-guyane.fr") == 0
|
||||
) {
|
||||
acad = "outremer.fr";
|
||||
}
|
||||
if (!acad.contains("ac-lyon.fr")) {
|
||||
//TODO: remove hack for test with openid
|
||||
/*if( acad.contains("orleans-tours.fr")) {
|
||||
return "tube-normandie.beta.education.fr";
|
||||
}*/
|
||||
return "tube-" + acad.replaceAll("ac-|\\.fr", "") + ".beta.education.fr";
|
||||
} else {
|
||||
return "tube.ac-lyon.fr";
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public boolean isOpenId() {
|
||||
return openId;
|
||||
}
|
||||
|
||||
}
|
@ -28,15 +28,16 @@ import retrofit2.http.Query;
|
||||
|
||||
interface MastodonService {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("apps")
|
||||
Call<Oauth> getOauth(
|
||||
@Query("client_name") String client_name,
|
||||
@Query("redirect_uris") String redirect_uris,
|
||||
@Query("scopes") String scopes,
|
||||
@Query("website") String website);
|
||||
@Field("client_name") String client_name,
|
||||
@Field("redirect_uris") String redirect_uris,
|
||||
@Field("scopes") String scopes,
|
||||
@Field("website") String website);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/oauth/token")
|
||||
@POST("oauth/token")
|
||||
Call<Token> createToken(
|
||||
@Field("grant_type") String grant_type,
|
||||
@Field("client_id") String client_id,
|
||||
@ -53,12 +54,13 @@ interface MastodonService {
|
||||
@Query("q") String messageURL
|
||||
);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("statuses")
|
||||
Call<Status> postReply(
|
||||
@Header("Authorization") String credentials,
|
||||
@Query("in_reply_to_id") String inReplyToId,
|
||||
@Query("status") String content,
|
||||
@Query("visibility") String visibility
|
||||
@Field("in_reply_to_id") String inReplyToId,
|
||||
@Field("status") String content,
|
||||
@Field("visibility") String visibility
|
||||
);
|
||||
|
||||
|
||||
|
@ -25,6 +25,7 @@ import android.os.Looper;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import app.fedilab.fedilabtube.MainActivity;
|
||||
import app.fedilab.fedilabtube.R;
|
||||
@ -36,6 +37,7 @@ import app.fedilab.fedilabtube.client.entities.Token;
|
||||
import app.fedilab.fedilabtube.helper.Helper;
|
||||
import app.fedilab.fedilabtube.sqlite.MastodonAccountDAO;
|
||||
import app.fedilab.fedilabtube.sqlite.Sqlite;
|
||||
import okhttp3.OkHttpClient;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
@ -49,6 +51,12 @@ public class RetrofitMastodonAPI {
|
||||
private String instance;
|
||||
private String token;
|
||||
|
||||
|
||||
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
public Status search(String url) throws Error {
|
||||
MastodonService mastodonService2 = init2();
|
||||
Call<Results> statusCall = mastodonService2.searchMessage(getToken(), url);
|
||||
@ -142,10 +150,25 @@ public class RetrofitMastodonAPI {
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private MastodonService init_no_api() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://" + instance)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(okHttpClient)
|
||||
.build();
|
||||
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
if (token == null) {
|
||||
token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
|
||||
}
|
||||
return retrofit.create(MastodonService.class);
|
||||
}
|
||||
|
||||
private MastodonService init() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(finalUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(okHttpClient)
|
||||
.build();
|
||||
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
if (token == null) {
|
||||
@ -158,6 +181,7 @@ public class RetrofitMastodonAPI {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(finalUrl2)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(okHttpClient)
|
||||
.build();
|
||||
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
if (token == null) {
|
||||
@ -223,7 +247,7 @@ public class RetrofitMastodonAPI {
|
||||
* @return Account
|
||||
*/
|
||||
public Token manageToken(OauthParams oauthParams) throws Error {
|
||||
MastodonService mastodonService = init();
|
||||
MastodonService mastodonService = init_no_api();
|
||||
Call<Token> createToken = mastodonService.createToken(
|
||||
oauthParams.getGrant_type(),
|
||||
oauthParams.getClient_id(),
|
||||
@ -274,7 +298,7 @@ public class RetrofitMastodonAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Status postAction(actionType type, Status status) throws Error {
|
||||
public Status postAction(actionType type, Status status) {
|
||||
MastodonService mastodonService = init();
|
||||
Call<Status> postAction = null;
|
||||
if (status != null) {
|
||||
|
@ -31,10 +31,12 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.fedilabtube.AccountActivity;
|
||||
import app.fedilab.fedilabtube.PeertubeActivity;
|
||||
import app.fedilab.fedilabtube.R;
|
||||
import app.fedilab.fedilabtube.ShowAccountActivity;
|
||||
import app.fedilab.fedilabtube.ShowChannelActivity;
|
||||
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
|
||||
import app.fedilab.fedilabtube.client.data.AccountData;
|
||||
import app.fedilab.fedilabtube.client.data.ChannelData;
|
||||
import app.fedilab.fedilabtube.client.data.NotificationData.Notification;
|
||||
@ -43,6 +45,8 @@ import app.fedilab.fedilabtube.fragment.DisplayNotificationsFragment;
|
||||
import app.fedilab.fedilabtube.helper.Helper;
|
||||
import app.fedilab.fedilabtube.helper.HelperInstance;
|
||||
|
||||
import static app.fedilab.fedilabtube.MainActivity.badgeCount;
|
||||
|
||||
|
||||
public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
@ -72,6 +76,11 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
holder.peertube_notif_pp.setVisibility(View.VISIBLE);
|
||||
AccountData.Account accountAction = null;
|
||||
ChannelData.Channel channelAction = null;
|
||||
if (notification.isRead()) {
|
||||
holder.unread.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
holder.unread.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (notification.getActorFollow() != null) {
|
||||
String profileUrl = notification.getActorFollow().getFollower().getAvatar() != null ? notification.getActorFollow().getFollower().getAvatar().getPath() : null;
|
||||
Helper.loadGiF(context, profileUrl, holder.peertube_notif_pp);
|
||||
@ -93,6 +102,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
accountAction.setDisplayName(actor.getDisplayName());
|
||||
accountAction.setHost(actor.getHost());
|
||||
accountAction.setUsername(actor.getName());
|
||||
holder.peertube_notif_message.setOnClickListener(v -> markAsRead(notification, position));
|
||||
} else if (notification.getComment() != null) { //Comment Notification
|
||||
String profileUrl = notification.getComment().getAccount().getAvatar() != null ? notification.getComment().getAccount().getAvatar().getPath() : null;
|
||||
Helper.loadGiF(context, profileUrl, holder.peertube_notif_pp);
|
||||
@ -112,6 +122,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
b.putString("video_id", notification.getComment().getVideo().getId());
|
||||
b.putString("video_uuid", notification.getComment().getVideo().getUuid());
|
||||
intent.putExtras(b);
|
||||
markAsRead(notification, position);
|
||||
context.startActivity(intent);
|
||||
});
|
||||
} else {
|
||||
@ -155,6 +166,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
b.putString("video_uuid", notification.getVideo().getUuid());
|
||||
intent.putExtras(b);
|
||||
context.startActivity(intent);
|
||||
markAsRead(notification, position);
|
||||
});
|
||||
} else if (notification.getVideoAbuse() != null && notification.getVideoAbuse().getVideo() != null) {
|
||||
message = context.getString(R.string.peertube_video_abuse, notification.getVideoAbuse().getVideo().getName());
|
||||
@ -163,6 +175,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
holder.peertube_notif_message.setText(Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY));
|
||||
else
|
||||
holder.peertube_notif_message.setText(Html.fromHtml(message));
|
||||
holder.peertube_notif_message.setOnClickListener(v -> markAsRead(notification, position));
|
||||
} else if (notification.getAbuse() != null) {
|
||||
clickableNotification = false;
|
||||
if (notification.getType() == DisplayNotificationsFragment.MY_VIDEO_REPPORT_SUCCESS) {
|
||||
@ -172,6 +185,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
holder.peertube_notif_message.setText(Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY));
|
||||
else
|
||||
holder.peertube_notif_message.setText(Html.fromHtml(message));
|
||||
holder.peertube_notif_message.setOnClickListener(v -> markAsRead(notification, position));
|
||||
}
|
||||
}
|
||||
holder.peertube_notif_date.setText(Helper.dateDiff(context, notification.getCreatedAt()));
|
||||
@ -197,6 +211,17 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
}
|
||||
}
|
||||
|
||||
private void markAsRead(Notification notification, int position) {
|
||||
if (!notification.isRead()) {
|
||||
notification.setRead(true);
|
||||
badgeCount--;
|
||||
if (context instanceof AccountActivity) {
|
||||
((AccountActivity) context).updateCounter();
|
||||
}
|
||||
notifyItemChanged(position);
|
||||
new Thread(() -> new RetrofitPeertubeAPI(context).markAsRead(notification.getId())).start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
@ -212,7 +237,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
ImageView peertube_notif_pp;
|
||||
TextView peertube_notif_message, peertube_notif_date;
|
||||
TextView peertube_notif_message, peertube_notif_date, unread;
|
||||
RelativeLayout main_container_trans;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
@ -221,6 +246,8 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
|
||||
peertube_notif_message = itemView.findViewById(R.id.peertube_notif_message);
|
||||
peertube_notif_date = itemView.findViewById(R.id.peertube_notif_date);
|
||||
main_container_trans = itemView.findViewById(R.id.container_trans);
|
||||
unread = itemView.findViewById(R.id.unread);
|
||||
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
|
@ -259,11 +259,9 @@ public class DisplayVideosFragment extends Fragment implements AccountsHorizonta
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (binding.swipeContainer != null) {
|
||||
binding.swipeContainer.setEnabled(false);
|
||||
binding.swipeContainer.setRefreshing(false);
|
||||
binding.swipeContainer.clearAnimation();
|
||||
}
|
||||
if (getActivity() != null) {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null && getView() != null) {
|
||||
@ -431,7 +429,7 @@ public class DisplayVideosFragment extends Fragment implements AccountsHorizonta
|
||||
}
|
||||
|
||||
public void manageVIewRelationship(APIResponse apiResponse) {
|
||||
if (apiResponse.getError() != null) {
|
||||
if (apiResponse.getError() != null || apiResponse.getRelationships() == null) {
|
||||
return;
|
||||
}
|
||||
if (relationship == null) {
|
||||
|
@ -34,6 +34,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import app.fedilab.fedilabtube.BuildConfig;
|
||||
import app.fedilab.fedilabtube.MainActivity;
|
||||
import app.fedilab.fedilabtube.R;
|
||||
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
|
||||
@ -315,7 +316,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
|
||||
CharSequence[] entriesTheme = arrayTheme.toArray(new CharSequence[0]);
|
||||
CharSequence[] entryValuesTheme = new CharSequence[3];
|
||||
final SharedPreferences sharedpref = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
int currentTheme = sharedpref.getInt(Helper.SET_THEME, Helper.DEFAULT_MODE);
|
||||
int currentTheme = sharedpref.getInt(Helper.SET_THEME, BuildConfig.default_theme);
|
||||
entryValuesTheme[0] = String.valueOf(Helper.LIGHT_MODE);
|
||||
entryValuesTheme[1] = String.valueOf(Helper.DARK_MODE);
|
||||
entryValuesTheme[2] = String.valueOf(Helper.DEFAULT_MODE);
|
||||
@ -407,7 +408,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
|
||||
set_video_in_list_choice.setChecked(videosInList);
|
||||
|
||||
//****** Allow Chromecast *******
|
||||
int cast = sharedpref.getInt(getString(R.string.set_cast_choice), 0);
|
||||
int cast = sharedpref.getInt(getString(R.string.set_cast_choice), BuildConfig.cast_enabled);
|
||||
SwitchPreference set_cast_choice = findPreference(getString(R.string.set_cast_choice));
|
||||
assert set_cast_choice != null;
|
||||
set_cast_choice.setChecked(cast == 1);
|
||||
|
@ -135,6 +135,7 @@ public class Helper {
|
||||
public static final String VIDEO_ID = "video_id_update";
|
||||
public static final String APP_PREFS = "app_prefs";
|
||||
public static final String CAST_ID = "D402501A";
|
||||
public static final String CAST_ID_BITTUBE = "CBA4A31D";
|
||||
public static final int VIDEOS_PER_PAGE = 10;
|
||||
public static final String RECEIVE_ACTION = "receive_action";
|
||||
public static final String SET_UNFOLLOW_VALIDATION = "set_unfollow_validation";
|
||||
@ -691,4 +692,6 @@ public class Helper {
|
||||
return String.format(Locale.getDefault(), "%s%s", df.format(rounded), context.getString(R.string.b));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -32,6 +32,64 @@ public class HelperAcadInstance {
|
||||
public static String SUBSCRIPTIONS = "ABONNEMENTS";
|
||||
public static String MYVIDEOS = "VIDEOS";
|
||||
|
||||
|
||||
public static String[] notOpenId = {
|
||||
"ac-aix-marseille.fr",
|
||||
"ac-amiens.fr",
|
||||
"ac-besancon.fr",
|
||||
"ac-bordeaux.fr",
|
||||
"clermont-ferrand.fr",
|
||||
"ac-corse.fr",
|
||||
"ac-creteil.fr",
|
||||
"ac-dijon.fr",
|
||||
"ac-grenoble.fr",
|
||||
"ac-lille.fr",
|
||||
"ac-limoges.fr",
|
||||
"ac-lyon.fr",
|
||||
"ac-mayotte.fr",
|
||||
"ac-montpellier.fr",
|
||||
"ac-nancy.fr",
|
||||
"ac-nantes.fr",
|
||||
"ac-orleans-tours.fr",
|
||||
"ac-paris.fr",
|
||||
"ac-poitiers.fr",
|
||||
"outremer.fr",
|
||||
"ac-rennes.fr",
|
||||
"ac-strasbourg.fr",
|
||||
"ac-toulouse.fr",
|
||||
"ac-versailles.fr"
|
||||
};
|
||||
|
||||
public static String[] academies = {
|
||||
"ac-aix-marseille.fr",
|
||||
"ac-amiens.fr",
|
||||
"ac-besancon.fr",
|
||||
"ac-bordeaux.fr",
|
||||
"clermont-ferrand.fr",
|
||||
"ac-corse.fr",
|
||||
"ac-creteil.fr",
|
||||
"ac-dijon.fr",
|
||||
"ac-grenoble.fr",
|
||||
"education.fr",
|
||||
"ac-lille.fr",
|
||||
"ac-limoges.fr",
|
||||
"ac-lyon.fr",
|
||||
"ac-mayotte.fr",
|
||||
"ac-montpellier.fr",
|
||||
"ac-nancy.fr",
|
||||
"ac-nantes.fr",
|
||||
"ac-normandie.fr",
|
||||
"ac-orleans-tours.fr",
|
||||
"ac-paris.fr",
|
||||
"ac-poitiers.fr",
|
||||
"outremer.fr",
|
||||
"ac-rennes.fr",
|
||||
"ac-strasbourg.fr",
|
||||
"ac-toulouse.fr",
|
||||
"ac-versailles.fr"
|
||||
};
|
||||
|
||||
|
||||
//List of available emails
|
||||
public static String[] valideEmails = {
|
||||
"ac-aix-marseille.fr",
|
||||
@ -71,10 +129,11 @@ public class HelperAcadInstance {
|
||||
"igesr.gouv.fr"
|
||||
};
|
||||
|
||||
|
||||
static {
|
||||
Map<String, String> is = new LinkedHashMap<>();
|
||||
is.put("Normandie", "tube-normandie.beta.education.fr");
|
||||
is.put("Enseignement professionnel", "tube-enseignement-professionnel.apps.education.fr");
|
||||
//TODO: remove comments when instances will be available
|
||||
/* is.put("Enseignement professionnel", "tube-enseignement-professionnel.apps.education.fr");
|
||||
is.put("Action éducative", "tube-action-educative.apps.education.fr");
|
||||
is.put("Numérique éducatif", "tube-numerique-educatif.apps.education.fr");
|
||||
is.put("Institutionnel", "tube-institutionnelle.apps.education.fr");
|
||||
@ -83,7 +142,7 @@ public class HelperAcadInstance {
|
||||
is.put("2d - arts, lettres et sciences humaines", "tube-2d-arts-lettres-sciences-humaines.apps.education.fr");
|
||||
is.put("Maternelle", "tube-maternelle.apps.education.fr");
|
||||
is.put("Cycle 2", "tube-cycle-2.apps.education.fr");
|
||||
is.put("Cycle 3", "tube-cycle-3.apps.education.fr");
|
||||
is.put("Cycle 3", "tube-cycle-3.apps.education.fr");*/
|
||||
instances_themes = Collections.unmodifiableMap(is);
|
||||
}
|
||||
|
||||
|
@ -30,10 +30,8 @@ public class HelperInstance {
|
||||
* @param context Context
|
||||
* @return String domain instance
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static String getLiveInstance(Context context) {
|
||||
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
|
||||
String acad;
|
||||
if (BuildConfig.FLAVOR.compareTo("fdroid_full") == 0 || BuildConfig.FLAVOR.compareTo("google_full") == 0) {
|
||||
return sharedpreferences.getString(Helper.PREF_INSTANCE, getDefaultInstance());
|
||||
} else if (BuildConfig.FLAVOR.compareTo("bittube") == 0) {
|
||||
@ -41,13 +39,15 @@ public class HelperInstance {
|
||||
} else if (BuildConfig.FLAVOR.compareTo("queermotion") == 0) {
|
||||
return sharedpreferences.getString(Helper.PREF_INSTANCE, "queermotion.org");
|
||||
} else {
|
||||
acad = sharedpreferences.getString(Helper.PREF_INSTANCE, "tube-institutionnelle.apps.education.fr");
|
||||
if (acad == null) {
|
||||
acad = "tube-institutionnelle.apps.education.fr";
|
||||
}
|
||||
return acad;
|
||||
String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, "tube.ac-lyon.fr");
|
||||
if (!instance.startsWith("tube")) {
|
||||
return "tube.ac-lyon.fr";
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a default instance host name depending of the device locale
|
||||
|
@ -107,7 +107,7 @@ public class MastodonPostActionsVM extends AndroidViewModel {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> statusMutableLiveData.setValue(statusReply);
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (Exception | Error e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
|
8
app/src/main/res/drawable/circle_red.xml
Normal file
8
app/src/main/res/drawable/circle_red.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@android:color/holo_red_dark" />
|
||||
<size
|
||||
android:width="10dp"
|
||||
android:height="10dp" />
|
||||
</shape>
|
10
app/src/main/res/drawable/ic_baseline_attach_money_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_attach_money_24.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_baseline_fiber_new_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_fiber_new_24.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/colorAccent"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,4H4C2.89,4 2.01,4.89 2.01,6L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6C22,4.89 21.11,4 20,4zM8.5,15H7.3l-2.55,-3.5V15H3.5V9h1.25l2.5,3.5V9H8.5V15zM13.5,10.26H11v1.12h2.5v1.26H11v1.11h2.5V15h-4V9h4V10.26zM20.5,14c0,0.55 -0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1V9h1.25v4.51h1.13V9.99h1.25v3.51h1.12V9h1.25V14z" />
|
||||
</vector>
|
@ -115,7 +115,7 @@
|
||||
android:id="@+id/developer_mastodon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="\@TubeLab@toot.fedilab.org"
|
||||
android:text="\@apps@toot.fedilab.org"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
@ -135,23 +135,6 @@
|
||||
android:textSize="16sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/github"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Github"
|
||||
android:textSize="16sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codeberg"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Codeberg"
|
||||
android:textSize="16sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@ -266,7 +249,7 @@
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="3"
|
||||
android:autoLink="web"
|
||||
android:text="https://github.com/stom79/TubeLab/issues"
|
||||
android:text="https://framagit.org/imattau/fedilab-tube/-/issues"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
||||
android:id="@+id/instance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
|
@ -81,6 +81,34 @@
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instance_picker_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/instances_picker"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/instance_picker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/instance_picker_title" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrierTop"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="instance_picker, login_instance_container" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/login_uid_container"
|
||||
android:layout_width="0dp"
|
||||
@ -88,7 +116,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/login_instance_container">
|
||||
app:layout_constraintTop_toBottomOf="@id/barrierTop">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/login_uid"
|
||||
@ -121,27 +149,7 @@
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instance_picker_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/instances_picker"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/instance_picker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/login_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/instance_picker_title" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
|
@ -44,16 +44,6 @@
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:fitsSystemWindows="true"
|
||||
>
|
||||
<ImageView
|
||||
android:id="@+id/instances"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_baseline_public_24"
|
||||
android:layout_gravity="start"
|
||||
android:contentDescription="@string/instance_choice"
|
||||
android:layout_marginEnd="5dp"
|
||||
app:tint="?attr/colorAccent" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -75,58 +75,9 @@
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cast_controller"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cast_play"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/play"
|
||||
android:src="@drawable/ic_baseline_play_arrow_32"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cast_loader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cast_loader_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/please_wait"
|
||||
android:textColor="?colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cast_loader_small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.github.ybq.android.spinkit.SpinKitView
|
||||
android:id="@+id/cast_loader_small"
|
||||
style="@style/progressBottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="18dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="5dp"
|
||||
app:SpinKit_Color="?colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/cast_loader_text"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<include
|
||||
android:id="@+id/min_controller"
|
||||
layout="@layout/min_controller" />
|
||||
|
||||
<app.fedilab.fedilabtube.webview.CustomWebview
|
||||
android:id="@+id/webview_video"
|
||||
@ -689,6 +640,5 @@
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="30dp" />
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
35
app/src/main/res/layout/counter_account_icon.xml
Normal file
35
app/src/main/res/layout/counter_account_icon.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/account"
|
||||
android:src="@drawable/ic_outline_account_circle_24" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/view_alert_red_circle"
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:background="@drawable/circle_red"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_alert_count_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="10sp"
|
||||
tools:text="3" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
@ -15,6 +15,7 @@
|
||||
see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
<FrameLayout 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">
|
||||
|
||||
@ -38,13 +39,27 @@
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/unread"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="invisible"
|
||||
app:drawableStartCompat="@drawable/ic_baseline_fiber_new_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/peertube_notif_date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="end"
|
||||
android:textAlignment="viewEnd" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/peertube_notif_message"
|
||||
|
@ -9,22 +9,21 @@
|
||||
android:title="@string/search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView" />
|
||||
<item
|
||||
android:id="@+id/action_account"
|
||||
app:actionLayout="@layout/counter_account_icon"
|
||||
android:title="@string/account"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_change_instance"
|
||||
android:icon="@drawable/ic_baseline_track_changes_24"
|
||||
android:title="@string/change_instance"
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_playlist"
|
||||
android:icon="@drawable/ic_baseline_playlist_play_24"
|
||||
android:title="@string/playlists"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_account"
|
||||
android:icon="@drawable/ic_outline_account_circle_24"
|
||||
android:title="@string/account"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_upload"
|
||||
android:icon="@drawable/ic_baseline_cloud_upload_24"
|
||||
@ -67,4 +66,10 @@
|
||||
android:icon="@drawable/ic_baseline_info_24"
|
||||
android:title="@string/about_the_app"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_donate"
|
||||
android:icon="@drawable/ic_baseline_attach_money_24"
|
||||
android:title="@string/make_a_donation"
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
|
@ -361,4 +361,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -357,4 +357,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -356,4 +356,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -357,4 +357,20 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
</resources>
|
||||
|
@ -357,4 +357,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -357,4 +357,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -356,4 +356,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -356,4 +356,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -357,4 +357,22 @@
|
||||
<string name="watermark">Watermerk</string>
|
||||
<string name="toast_code_error">Er is een fout opgetreden! De instantie gaf geen autorisatiecode terug!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> extern account verbonden met de app.\n\nU kunt doorgaan naar bepaalde acties.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -359,4 +359,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -357,4 +357,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -358,4 +358,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -133,9 +133,9 @@
|
||||
<string name="action_follow">Подписка</string>
|
||||
<string name="action_mute">Игнорировать</string>
|
||||
<string name="unlimited">Без ограничений</string>
|
||||
<string name="peers">%1$d Peers</string>
|
||||
<string name="b">B</string>
|
||||
<string name="kb">KB</string>
|
||||
<string name="peers">%1$d пиров</string>
|
||||
<string name="b">Б</string>
|
||||
<string name="kb">КБ</string>
|
||||
<string name="mb">МБ</string>
|
||||
<string name="gb">ГБ</string>
|
||||
<string name="total_video_quota">Общая квота видео</string>
|
||||
@ -357,6 +357,24 @@
|
||||
<string name="instance_not_availabe">Экземпляр недоступен!</string>
|
||||
<string name="max_tag_size">На видео не должно быть более 5 тегов!</string>
|
||||
<string name="watermark">Водяной знак</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="toast_code_error">Произошла ошибка! Экземпляр не вернул код авторизации!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> удаленная учетная запись, связанная с приложением.\n\nВы можете перейти к некоторым ограниченным действиям.</string>
|
||||
<string name="donate">Пожертвование</string>
|
||||
<string name="my_donations">Мои пожертвования</string>
|
||||
<string name="one_time_donation_text">Здесь вы можете сделать единовременное пожертвование для поддержки разработки приложения. Это действие не принесет дополнительных возможностей!</string>
|
||||
<string name="recurrent_donation_text">Здесь вы можете сделать единовременное пожертвование для поддержки разработки приложения. Это действие не принесет дополнительных возможностей!</string>
|
||||
<string name="make_a_donation">Сделать пожертвование</string>
|
||||
<string name="donations_description">Здесь вы найдете список ваших периодических пожертвований, сделанных для поддержки разработки приложения! Спасибо!</string>
|
||||
<string name="support_the_app">Поддержка приложения</string>
|
||||
<string name="donation_cancelled">Пожертвование было отменено!</string>
|
||||
<string name="donation_succeeded_null">Благодарим Вас за ваше пожертвование!</string>
|
||||
<string name="donation_succeeded">Спасибо за пожертвование в %1$s!</string>
|
||||
<string name="one_time">Один раз</string>
|
||||
<string name="my_subscriptions">Моя подписка</string>
|
||||
<string name="month">Месяц</string>
|
||||
<string name="subscription_cancelled">Подписка отменена!</string>
|
||||
<string name="cancel_subscription">Отменить подписку</string>
|
||||
<string name="cancel_subscription_confirm">Вы уверены, что хотите отменить эту подписку?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Вы уверены, что хотите отметить все уведомления как прочитанные?</string>
|
||||
<string name="mark_all_as_read">Отметить все как прочитанные</string>
|
||||
</resources>
|
||||
|
@ -357,4 +357,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -355,4 +355,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -356,4 +356,22 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
</resources>
|
||||
|
@ -455,5 +455,23 @@
|
||||
<string name="watermark">Watermark</string>
|
||||
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string>
|
||||
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="my_donations">My donations</string>
|
||||
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string>
|
||||
<string name="make_a_donation">Make a donation</string>
|
||||
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string>
|
||||
<string name="support_the_app">Support the app</string>
|
||||
<string name="donation_cancelled">Donation has been cancelled!</string>
|
||||
<string name="donation_succeeded_null">Thank you for your donation!</string>
|
||||
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
|
||||
<string name="one_time">One time</string>
|
||||
<string name="my_subscriptions">My subscription</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="subscription_cancelled">Subscription cancelled!</string>
|
||||
<string name="cancel_subscription">Cancel subscription</string>
|
||||
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
|
||||
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
|
||||
</resources>
|
@ -0,0 +1,261 @@
|
||||
package app.fedilab.fedilabtube;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.fedilabtube.client.data.VideoData;
|
||||
import app.fedilab.fedilabtube.databinding.ActivityMainBinding;
|
||||
import app.fedilab.fedilabtube.helper.Helper;
|
||||
import su.litvak.chromecast.api.v2.ChromeCast;
|
||||
import su.litvak.chromecast.api.v2.ChromeCasts;
|
||||
import su.litvak.chromecast.api.v2.ChromeCastsListener;
|
||||
import su.litvak.chromecast.api.v2.MediaStatus;
|
||||
|
||||
public abstract class BaseMainActivity extends AppCompatActivity implements ChromeCastsListener {
|
||||
|
||||
public static List<ChromeCast> chromeCasts;
|
||||
public static ChromeCast chromeCast;
|
||||
public static boolean chromecastActivated = false;
|
||||
protected ActivityMainBinding binding;
|
||||
private BroadcastReceiver manage_chromecast;
|
||||
private VideoData.Video castedTube;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
View view = binding.getRoot();
|
||||
setContentView(view);
|
||||
ChromeCastsListener chromeCastsListener = this;
|
||||
ChromeCasts.registerListener(chromeCastsListener);
|
||||
|
||||
|
||||
binding.castClose.setOnClickListener(v -> {
|
||||
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
|
||||
Bundle b = new Bundle();
|
||||
b.putInt("displayed", 0);
|
||||
intentBC.putExtras(b);
|
||||
LocalBroadcastManager.getInstance(BaseMainActivity.this).sendBroadcast(intentBC);
|
||||
});
|
||||
|
||||
binding.castTogglePlay.setOnClickListener(v -> {
|
||||
if (chromeCast != null) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> binding.castTogglePlay.setVisibility(View.GONE);
|
||||
mainHandler.post(myRunnable);
|
||||
int icon = -1;
|
||||
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
|
||||
chromeCast.pause();
|
||||
icon = R.drawable.ic_baseline_play_arrow_32;
|
||||
} else if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
|
||||
chromeCast.play();
|
||||
icon = R.drawable.ic_baseline_pause_32;
|
||||
}
|
||||
if (icon != -1) {
|
||||
int finalIcon = icon;
|
||||
myRunnable = () -> binding.castTogglePlay.setImageResource(finalIcon);
|
||||
mainHandler.post(myRunnable);
|
||||
}
|
||||
myRunnable = () -> binding.castTogglePlay.setVisibility(View.VISIBLE);
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
manage_chromecast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Bundle b = intent.getExtras();
|
||||
assert b != null;
|
||||
int state = b.getInt("state_asked", -1);
|
||||
int displayed = b.getInt("displayed", -1);
|
||||
castedTube = b.getParcelable("castedTube");
|
||||
|
||||
if (state == 1) {
|
||||
discoverCast();
|
||||
} else if (state == 0) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (chromeCast != null) {
|
||||
chromeCast.stopApp();
|
||||
chromeCast.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (displayed == 1) {
|
||||
chromecastActivated = true;
|
||||
if (castedTube != null) {
|
||||
binding.castInfo.setVisibility(View.VISIBLE);
|
||||
Helper.loadGiF(BaseMainActivity.this, castedTube.getThumbnailPath(), binding.castView);
|
||||
binding.castTitle.setText(castedTube.getTitle());
|
||||
binding.castDescription.setText(castedTube.getDescription());
|
||||
}
|
||||
} else if (displayed == 0) {
|
||||
chromecastActivated = false;
|
||||
binding.castInfo.setVisibility(View.GONE);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (chromeCast != null) {
|
||||
chromeCast.stopApp();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
};
|
||||
LocalBroadcastManager.getInstance(BaseMainActivity.this).registerReceiver(manage_chromecast, new IntentFilter(Helper.RECEIVE_CAST_SETTINGS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newChromeCastDiscovered(ChromeCast chromeCast) {
|
||||
if (chromeCasts == null) {
|
||||
chromeCasts = new ArrayList<>();
|
||||
chromeCasts.add(chromeCast);
|
||||
} else {
|
||||
boolean canBeAdded = true;
|
||||
for (ChromeCast cast : chromeCasts) {
|
||||
if (cast.getName().compareTo(chromeCast.getName()) == 0) {
|
||||
canBeAdded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canBeAdded) {
|
||||
chromeCasts.add(chromeCast);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (chromeCast.isAppRunning(Helper.CAST_ID) && chromeCast.getMediaStatus() != null && chromeCast.getMediaStatus().playerState != null) {
|
||||
if (binding.castInfo.getVisibility() == View.GONE) {
|
||||
binding.castInfo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void chromeCastRemoved(ChromeCast chromeCast) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
ChromeCasts.unregisterListener(this);
|
||||
if (manage_chromecast != null) {
|
||||
LocalBroadcastManager.getInstance(BaseMainActivity.this).unregisterReceiver(manage_chromecast);
|
||||
|
||||
new Thread(() -> {
|
||||
if (chromeCasts != null && chromeCasts.size() > 0) {
|
||||
for (ChromeCast cast : chromeCasts) {
|
||||
try {
|
||||
cast.stopApp();
|
||||
cast.disconnect();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (chromeCasts != null) {
|
||||
chromeCasts = null;
|
||||
}
|
||||
if (chromeCast != null) {
|
||||
chromeCast = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Method for discovering cast devices
|
||||
public void discoverCast() {
|
||||
|
||||
new Thread(() -> {
|
||||
if (chromeCasts != null) {
|
||||
for (ChromeCast cast : chromeCasts) {
|
||||
try {
|
||||
cast.disconnect();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
chromeCasts = null;
|
||||
}
|
||||
chromeCasts = new ArrayList<>();
|
||||
try {
|
||||
List<NetworkInterface> interfaces;
|
||||
interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface ni : interfaces) {
|
||||
if ((!ni.isLoopback()) && ni.isUp() && (ni.getName().equals("wlan0"))) {
|
||||
Enumeration<InetAddress> inetAddressEnumeration = ni.getInetAddresses();
|
||||
while (inetAddressEnumeration.hasMoreElements()) {
|
||||
InetAddress inetAddress = inetAddressEnumeration.nextElement();
|
||||
ChromeCasts.restartDiscovery(inetAddress);
|
||||
int tryFind = 0;
|
||||
while (ChromeCasts.get().isEmpty() && tryFind < 5) {
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(1000);
|
||||
tryFind++;
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ChromeCasts.stopDiscovery();
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = this::invalidateOptionsMenu;
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
package app.fedilab.fedilabtube;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import app.fedilab.fedilabtube.client.data.VideoData;
|
||||
import app.fedilab.fedilabtube.databinding.ActivityPeertubeBinding;
|
||||
import app.fedilab.fedilabtube.helper.Helper;
|
||||
import su.litvak.chromecast.api.v2.ChromeCast;
|
||||
import su.litvak.chromecast.api.v2.MediaStatus;
|
||||
import su.litvak.chromecast.api.v2.Status;
|
||||
|
||||
import static app.fedilab.fedilabtube.helper.Helper.CAST_ID;
|
||||
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
public class BasePeertubeActivity extends AppCompatActivity {
|
||||
|
||||
protected ActivityPeertubeBinding binding;
|
||||
protected VideoData.Video peertube;
|
||||
protected SimpleExoPlayer player;
|
||||
protected String videoURL;
|
||||
protected String subtitlesStr;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
|
||||
View view = binding.getRoot();
|
||||
setContentView(view);
|
||||
binding.minController.castPlay.setOnClickListener(v -> {
|
||||
binding.minController.castLoader.setVisibility(View.VISIBLE);
|
||||
if (BaseMainActivity.chromeCast != null) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
int icon = -1;
|
||||
if (BaseMainActivity.chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
|
||||
BaseMainActivity.chromeCast.pause();
|
||||
icon = R.drawable.ic_baseline_play_arrow_32;
|
||||
} else if (BaseMainActivity.chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
|
||||
BaseMainActivity.chromeCast.play();
|
||||
icon = R.drawable.ic_baseline_pause_32;
|
||||
}
|
||||
if (icon != -1) {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
int finalIcon = icon;
|
||||
Runnable myRunnable = () -> binding.minController.castPlay.setImageResource(finalIcon);
|
||||
mainHandler.post(myRunnable);
|
||||
}
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> binding.minController.castLoader.setVisibility(View.GONE);
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_cast) {
|
||||
if (BaseMainActivity.chromeCasts != null && BaseMainActivity.chromeCasts.size() > 0) {
|
||||
String[] chromecast_choice = new String[BaseMainActivity.chromeCasts.size()];
|
||||
AlertDialog.Builder alt_bld = new AlertDialog.Builder(this);
|
||||
alt_bld.setTitle(R.string.chromecast_choice);
|
||||
int i = 0;
|
||||
for (ChromeCast cc : BaseMainActivity.chromeCasts) {
|
||||
chromecast_choice[i] = cc.getTitle();
|
||||
i++;
|
||||
}
|
||||
i = 0;
|
||||
for (ChromeCast cc : BaseMainActivity.chromeCasts) {
|
||||
if (BaseMainActivity.chromecastActivated && cc.isConnected()) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
alt_bld.setSingleChoiceItems(chromecast_choice, i, (dialog, position) -> {
|
||||
BaseMainActivity.chromeCast = BaseMainActivity.chromeCasts.get(position);
|
||||
new Thread(() -> {
|
||||
if (BaseMainActivity.chromeCast != null) {
|
||||
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
|
||||
Bundle b = new Bundle();
|
||||
if (BaseMainActivity.chromecastActivated) {
|
||||
b.putInt("displayed", 0);
|
||||
intentBC.putExtras(b);
|
||||
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC);
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> {
|
||||
binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
|
||||
binding.minController.castMiniController.setVisibility(View.GONE);
|
||||
};
|
||||
mainHandler.post(myRunnable);
|
||||
|
||||
} else {
|
||||
b.putInt("displayed", 1);
|
||||
b.putParcelable("castedTube", peertube);
|
||||
intentBC.putExtras(b);
|
||||
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC);
|
||||
try {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> {
|
||||
invalidateOptionsMenu();
|
||||
binding.minController.castLoader.setVisibility(View.VISIBLE);
|
||||
player.setPlayWhenReady(false);
|
||||
binding.doubleTapPlayerView.setVisibility(View.GONE);
|
||||
binding.minController.castMiniController.setVisibility(View.VISIBLE);
|
||||
dialog.dismiss();
|
||||
if (videoURL != null) {
|
||||
if (player != null && player.getCurrentPosition() > 0) {
|
||||
videoURL += "?start=" + (player.getCurrentPosition() / 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
mainHandler.post(myRunnable);
|
||||
if (!BaseMainActivity.chromeCast.isConnected()) {
|
||||
BaseMainActivity.chromeCast.connect();
|
||||
}
|
||||
myRunnable = this::invalidateOptionsMenu;
|
||||
mainHandler.post(myRunnable);
|
||||
Status status = BaseMainActivity.chromeCast.getStatus();
|
||||
if (BaseMainActivity.chromeCast.isAppAvailable(CAST_ID) && !status.isAppRunning(CAST_ID)) {
|
||||
BaseMainActivity.chromeCast.launchApp(CAST_ID);
|
||||
}
|
||||
if (videoURL != null) {
|
||||
String mime = MimeTypeMap.getFileExtensionFromUrl(videoURL);
|
||||
BaseMainActivity.chromeCast.setRequestTimeout(60000);
|
||||
BaseMainActivity.chromeCast.load(peertube.getTitle(), null, videoURL, mime);
|
||||
BaseMainActivity.chromeCast.play();
|
||||
binding.minController.castPlay.setImageResource(R.drawable.ic_baseline_pause_32);
|
||||
}
|
||||
myRunnable = () -> binding.minController.castLoader.setVisibility(View.GONE);
|
||||
mainHandler.post(myRunnable);
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Runnable myRunnable = () -> {
|
||||
invalidateOptionsMenu();
|
||||
dialog.dismiss();
|
||||
};
|
||||
mainHandler.post(myRunnable);
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
alt_bld.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
AlertDialog alert = alt_bld.create();
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.video_menu, menu);
|
||||
MenuItem castItem = menu.findItem(R.id.action_cast);
|
||||
if (BaseMainActivity.chromeCasts != null && BaseMainActivity.chromeCasts.size() > 0) {
|
||||
castItem.setVisible(true);
|
||||
if (BaseMainActivity.chromeCast != null && BaseMainActivity.chromeCast.isConnected()) {
|
||||
castItem.setIcon(R.drawable.ic_baseline_cast_connected_24);
|
||||
} else {
|
||||
castItem.setIcon(R.drawable.ic_baseline_cast_24);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.fedilab.fedilabtube.provider;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
public class CastOptionsProvider {
|
||||
}
|
54
app/src/no_google_cast_lib/res/layout/min_controller.xml
Normal file
54
app/src/no_google_cast_lib/res/layout/min_controller.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?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:id="@+id/castMiniController"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cast_play"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/play"
|
||||
android:src="@drawable/ic_baseline_play_arrow_32"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cast_loader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cast_loader_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/please_wait"
|
||||
android:textColor="?colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cast_loader_small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.github.ybq.android.spinkit.SpinKitView
|
||||
android:id="@+id/cast_loader_small"
|
||||
style="@style/progressBottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="18dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="5dp"
|
||||
app:SpinKit_Color="?colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/cast_loader_text"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,20 @@
|
||||
package app.fedilab.fedilabtube;
|
||||
/* Copyright 2021 Thomas Schneider
|
||||
*
|
||||
* This file is a part of TubeLab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
//Do nothing
|
||||
public class DonationActivity {
|
||||
|
||||
}
|
@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||
classpath 'com.android.tools.build:gradle:4.1.2'
|
||||
def nav_version = "2.3.0"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
||||
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
|
||||
|
23
frostwire-jlibtorrent/.gitignore
vendored
Normal file
23
frostwire-jlibtorrent/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
*~
|
||||
.DS_Store
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea
|
||||
.gradle
|
||||
/build
|
||||
/out
|
||||
/android/build
|
||||
/android/src/main/jniLibs
|
||||
/java/out/
|
||||
*.o
|
||||
/jlibtorrent.xcodeproj/xcuserdata/
|
||||
/obj
|
||||
frostwire-jlibtorrent.i*
|
||||
/test-libtorrent
|
||||
/swig/bin
|
||||
/swig/build
|
||||
/libjlibtorrent*.dylib
|
||||
/libjlibtorrent*.so
|
||||
/jlibtorrent*.dll
|
||||
/node/jlibtorrent.node
|
||||
/dht_shell.dat
|
259
frostwire-jlibtorrent/.travis.yml
Normal file
259
frostwire-jlibtorrent/.travis.yml
Normal file
@ -0,0 +1,259 @@
|
||||
language: cpp
|
||||
dist: eoan
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: os_build=android os_arch=arm android_api=19
|
||||
- env: os_build=android os_arch=arm64 android_api=21
|
||||
- env: os_build=android os_arch=x86 android_api=19
|
||||
- env: os_build=android os_arch=x86_64 android_api=21
|
||||
- env: os_build=linux os_arch=x86
|
||||
- env: os_build=linux os_arch=x86_64
|
||||
- env: os_build=windows os_arch=x86
|
||||
- env: os_build=windows os_arch=x86_64
|
||||
- os: osx
|
||||
env: os_build=macosx os_arch=x86_64
|
||||
osx_image: xcode11.2
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- wget -nv -O boost.zip https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.zip
|
||||
- unzip -qq boost.zip
|
||||
- cd boost_1_73_0
|
||||
- ls -lth
|
||||
- ./bootstrap.sh
|
||||
- ./b2
|
||||
- cd ..
|
||||
- export BOOST_ROOT=$PWD/boost_1_73_0
|
||||
- export BOOST_BUILD_PATH=$BOOST_ROOT
|
||||
|
||||
# openssl: download
|
||||
- wget -nv -O openssl.tar.gz https://www.openssl.org/source/openssl-1.1.1i.tar.gz
|
||||
- tar xzf openssl.tar.gz
|
||||
- export OPENSSL_SOURCE=$PWD/openssl-1.1.1i
|
||||
# fix source code
|
||||
# avoid GDI dependency in windows, fix double return statement on threads_none.c
|
||||
- if [ $os_build == "windows" ]; then
|
||||
sed -i 's/if defined(_WIN32_WINNT) && _WIN32_WINNT>=0x0333/if 0/g' $OPENSSL_SOURCE/crypto/cryptlib.c;
|
||||
sed -i 's/MessageBox.*//g' $OPENSSL_SOURCE/crypto/cryptlib.c;
|
||||
sed -i 's/return return 0;/return 0;/g' $OPENSSL_SOURCE/crypto/threads_none.c;
|
||||
fi
|
||||
|
||||
# libtorrent: download and checkout revision
|
||||
- git clone https://github.com/arvidn/libtorrent
|
||||
- cd libtorrent
|
||||
- git checkout 77172672c8db2f6bcef17d867d9a81faae77ec1e
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
- cd ..
|
||||
- export LIBTORRENT_ROOT=$PWD/libtorrent
|
||||
|
||||
# android ndk: download and create toolchain
|
||||
- if [ $os_build == "android" ]; then
|
||||
wget -nv -O android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21d-linux-x86_64.zip;
|
||||
echo "Extracting NDK...wait";
|
||||
unzip -qq android-ndk.zip;
|
||||
export NDK=$PWD/android-ndk-r21d;
|
||||
$NDK/build/tools/make_standalone_toolchain.py --arch $os_arch --api $android_api --stl libc++ --install-dir android-toolchain;
|
||||
export ANDROID_TOOLCHAIN=$PWD/android-toolchain;
|
||||
fi
|
||||
|
||||
- if [[ $os_build == "linux" || $os_build == "windows" ]]; then
|
||||
sudo apt-get install -qq g++-7 g++-7-multilib;
|
||||
fi
|
||||
|
||||
- if [[ $os_build == "linux" && $os_arch == "x86" ]]; then
|
||||
sudo apt-get install -qq libc6-dev-i386;
|
||||
sudo apt-get install -qq lib32gcc-5-dev;
|
||||
sudo apt-get install -qq lib32stdc++-5-dev;
|
||||
sudo ln -s /usr/include/asm-generic /usr/include/asm;
|
||||
fi
|
||||
|
||||
# linux cross compilation tools for windows development
|
||||
# remove files related to libwinpthread dll
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
|
||||
sudo apt-get install -qq g++-mingw-w64-i686;
|
||||
fi
|
||||
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
|
||||
sudo apt-get install -qq g++-mingw-w64-x86-64;
|
||||
fi
|
||||
|
||||
# install gradle in macOS to run the unit tests
|
||||
- if [ $os_build == "macosx" ]; then
|
||||
brew update > /dev/null && brew install gradle;
|
||||
fi
|
||||
|
||||
# openssl
|
||||
- 'export OPENSSL_NO_OPTS="no-afalgeng no-async no-autoalginit no-autoerrinit
|
||||
no-capieng no-cms no-comp no-deprecated no-dgram no-dso no-dtls
|
||||
no-dynamic-engine no-egd no-engine no-err no-filenames no-gost no-hw
|
||||
no-makedepend no-multiblock no-nextprotoneg no-posix-io no-psk
|
||||
no-rdrand no-sctp no-shared no-sock no-srp no-srtp no-static-engine
|
||||
no-stdio no-threads no-ui-console no-zlib no-zlib-dynamic
|
||||
-fno-strict-aliasing -fvisibility=hidden -Os"'
|
||||
# android-arm
|
||||
- if [[ $os_build == "android" && $os_arch == "arm" ]]; then
|
||||
export CC=$ANDROID_TOOLCHAIN/bin/arm-linux-androideabi-clang;
|
||||
export run_openssl_configure="./Configure linux-armv4 ${OPENSSL_NO_OPTS} -march=armv7-a -mfpu=neon -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# android-arm64
|
||||
- if [[ $os_build == "android" && $os_arch == "arm64" ]]; then
|
||||
export CC=$ANDROID_TOOLCHAIN/bin/aarch64-linux-android-clang;
|
||||
export run_openssl_configure="./Configure linux-aarch64 ${OPENSSL_NO_OPTS} -march=armv8-a+crypto -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# android-x86
|
||||
- if [[ $os_build == "android" && $os_arch == "x86" ]]; then
|
||||
export CC=$ANDROID_TOOLCHAIN/bin/i686-linux-android-clang;
|
||||
export run_openssl_configure="./Configure linux-elf ${OPENSSL_NO_OPTS} -fPIC -mstackrealign --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# android-x86_64
|
||||
- if [[ $os_build == "android" && $os_arch == "x86_64" ]]; then
|
||||
export CC=$ANDROID_TOOLCHAIN/bin/x86_64-linux-android-clang;
|
||||
export run_openssl_configure="./Configure linux-x86_64 ${OPENSSL_NO_OPTS} -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# linux-x86
|
||||
- if [[ $os_build == "linux" && $os_arch == "x86" ]]; then
|
||||
export CC=gcc-7;
|
||||
export run_openssl_configure="./Configure linux-elf ${OPENSSL_NO_OPTS} -fPIC -m32 --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# linux-x86_64
|
||||
- if [[ $os_build == "linux" && $os_arch == "x86_64" ]]; then
|
||||
export CC=gcc-7;
|
||||
export run_openssl_configure="./Configure linux-x86_64 ${OPENSSL_NO_OPTS} -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# windows-x86
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
|
||||
export CC=i686-w64-mingw32-gcc-posix;
|
||||
export run_openssl_configure="./Configure mingw ${OPENSSL_NO_OPTS} --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# windows-x86_64
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
|
||||
export CC=x86_64-w64-mingw32-gcc-posix;
|
||||
export run_openssl_configure="./Configure mingw64 ${OPENSSL_NO_OPTS} --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
# macosx
|
||||
- if [ $os_build == "macosx" ]; then
|
||||
export run_openssl_configure="./Configure darwin64-x86_64-cc ${OPENSSL_NO_OPTS} --prefix=$OPENSSL_SOURCE/../openssl";
|
||||
fi
|
||||
|
||||
# jlibtorrent
|
||||
# android-arm
|
||||
- if [[ $os_build == "android" && $os_arch == "arm" ]]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-arm-config.jam variant=release toolset=clang-linux-arm target-os=android location=bin/release/android/armeabi-v7a";
|
||||
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-objcopy --only-keep-debug bin/release/android/armeabi-v7a/libjlibtorrent.so bin/release/android/armeabi-v7a/libjlibtorrent.so.debug";
|
||||
export run_strip="${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-strip --strip-unneeded -x -g bin/release/android/armeabi-v7a/libjlibtorrent.so";
|
||||
export run_readelf="${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-readelf -d bin/release/android/armeabi-v7a/libjlibtorrent.so";
|
||||
export PATH=$ANDROID_TOOLCHAIN/arm-linux-androideabi/bin:$PATH;
|
||||
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/arm-linux-androideabi-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
|
||||
fi
|
||||
# android-arm64
|
||||
- if [[ $os_build == "android" && $os_arch == "arm64" ]]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-arm64-config.jam variant=release toolset=clang-arm64 target-os=android location=bin/release/android/arm64-v8a";
|
||||
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/aarch64-linux-android-objcopy --only-keep-debug bin/release/android/arm64-v8a/libjlibtorrent.so bin/release/android/arm64-v8a/libjlibtorrent.so.debug";
|
||||
export run_strip="${ANDROID_TOOLCHAIN}/bin/aarch64-linux-android-strip --strip-unneeded -x bin/release/android/arm64-v8a/libjlibtorrent.so";
|
||||
export run_readelf="${ANDROID_TOOLCHAIN}/bin/aarch64-linux-android-readelf -d bin/release/android/arm64-v8a/libjlibtorrent.so";
|
||||
export PATH=$ANDROID_TOOLCHAIN/aarch64-linux-android/bin:$PATH;
|
||||
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/aarch64-linux-android-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
|
||||
fi
|
||||
# android-x86
|
||||
- if [[ $os_build == "android" && $os_arch == "x86" ]]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-x86-config.jam variant=release toolset=clang-x86 target-os=android location=bin/release/android/x86";
|
||||
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/i686-linux-android-objcopy --only-keep-debug bin/release/android/x86/libjlibtorrent.so bin/release/android/x86/libjlibtorrent.so.debug";
|
||||
export run_strip="${ANDROID_TOOLCHAIN}/bin/i686-linux-android-strip --strip-unneeded -x -g bin/release/android/x86/libjlibtorrent.so";
|
||||
export run_readelf="${ANDROID_TOOLCHAIN}/bin/i686-linux-android-readelf -d bin/release/android/x86/libjlibtorrent.so";
|
||||
export PATH=$ANDROID_TOOLCHAIN/i686-linux-android/bin:$PATH;
|
||||
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/i686-linux-android-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
|
||||
fi
|
||||
# android-x86_64
|
||||
- if [[ $os_build == "android" && $os_arch == "x86_64" ]]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-x86_64-config.jam variant=release toolset=clang-x86_64 target-os=android location=bin/release/android/x86_64";
|
||||
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/x86_64-linux-android-objcopy --only-keep-debug bin/release/android/x86_64/libjlibtorrent.so bin/release/android/x86_64/libjlibtorrent.so.debug";
|
||||
export run_strip="${ANDROID_TOOLCHAIN}/bin/x86_64-linux-android-strip --strip-unneeded -x bin/release/android/x86_64/libjlibtorrent.so";
|
||||
export run_readelf="${ANDROID_TOOLCHAIN}/bin/x86_64-linux-android-readelf -d bin/release/android/x86_64/libjlibtorrent.so";
|
||||
export PATH=$ANDROID_TOOLCHAIN/x86_64-linux-android/bin:$PATH;
|
||||
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/x86_64-linux-android-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
|
||||
fi
|
||||
# linux-x86
|
||||
- if [[ $os_build == "linux" && $os_arch == "x86" ]]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/linux-x86-config.jam variant=release toolset=gcc-x86 target-os=linux location=bin/release/linux/x86";
|
||||
export run_objcopy="objcopy --only-keep-debug bin/release/linux/x86/libjlibtorrent.so bin/release/linux/x86/libjlibtorrent.so.debug";
|
||||
export run_strip="strip --strip-unneeded -x bin/release/linux/x86/libjlibtorrent.so";
|
||||
export run_readelf="readelf -d bin/release/linux/x86/libjlibtorrent.so";
|
||||
fi
|
||||
# linux-x86_64
|
||||
- if [[ $os_build == "linux" && $os_arch == "x86_64" ]]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/linux-x86_64-config.jam variant=release toolset=gcc-x86_64 target-os=linux location=bin/release/linux/x86_64";
|
||||
export run_objcopy="objcopy --only-keep-debug bin/release/linux/x86_64/libjlibtorrent.so bin/release/linux/x86_64/libjlibtorrent.so.debug";
|
||||
export run_strip="strip --strip-unneeded -x bin/release/linux/x86_64/libjlibtorrent.so";
|
||||
export run_readelf="readelf -d bin/release/linux/x86_64/libjlibtorrent.so";
|
||||
fi
|
||||
# windows-x86
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
|
||||
sed -i 's/ JNICALL Java_com_frostwire/ JNICALL _Java_com_frostwire/g' swig/libtorrent_jni.cpp;
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/windows-x86-config.jam variant=release toolset=gcc-x86 target-os=windows location=bin/release/windows/x86";
|
||||
export run_strip="i686-w64-mingw32-strip --strip-unneeded -x bin/release/windows/x86/libjlibtorrent.dll";
|
||||
export run_readelf="eval objdump -p bin/release/windows/x86/jlibtorrent.dll | grep DLL";
|
||||
fi
|
||||
# windows-x86_64
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/windows-x86_64-config.jam variant=release toolset=gcc-x86_64 target-os=windows location=bin/release/windows/x86_64";
|
||||
export run_strip="x86_64-w64-mingw32-strip --strip-unneeded -x bin/release/windows/x86_64/libjlibtorrent.dll";
|
||||
export run_readelf="eval objdump -p bin/release/windows/x86_64/jlibtorrent.dll | grep DLL";
|
||||
fi
|
||||
# macosx
|
||||
- if [ $os_build == "macosx" ]; then
|
||||
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/macosx-x86_64-config.jam variant=release toolset=darwin-x86_64 target-os=darwin location=bin/release/macosx/x86_64";
|
||||
export run_strip="strip -S -x bin/release/macosx/x86_64/libjlibtorrent.dylib";
|
||||
export run_readelf="otool -L bin/release/macosx/x86_64/libjlibtorrent.dylib";
|
||||
fi
|
||||
|
||||
script:
|
||||
|
||||
- cd $OPENSSL_SOURCE
|
||||
- $run_openssl_configure
|
||||
- echo "Compiling openssl...(remove &> /dev/null to see output)"
|
||||
- make #&> /dev/null
|
||||
- make install_sw &> /dev/null
|
||||
- cd ..
|
||||
- export OPENSSL_ROOT=$PWD/openssl
|
||||
|
||||
- cd swig
|
||||
- $run_bjam
|
||||
- $run_objcopy
|
||||
- $run_strip
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
|
||||
mv bin/release/windows/x86/libjlibtorrent.dll bin/release/windows/x86/jlibtorrent.dll;
|
||||
fi
|
||||
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
|
||||
mv bin/release/windows/x86_64/libjlibtorrent.dll bin/release/windows/x86_64/jlibtorrent.dll;
|
||||
fi
|
||||
- if [ $os_build == "macosx" ]; then
|
||||
cd ..;
|
||||
cp swig/bin/release/macosx/x86_64/libjlibtorrent.dylib .;
|
||||
gradle test;
|
||||
cd swig;
|
||||
fi
|
||||
- $run_readelf
|
||||
- cd ..
|
||||
|
||||
before_deploy:
|
||||
- if [ -d swig/bin ]; then
|
||||
cd swig/bin;
|
||||
find . -type f | egrep -v '.*\.so$|.*\.dll$|.*\.dylib$|.*\.debug$' | xargs rm;
|
||||
find . -empty -type d | xargs rm -r;
|
||||
fi
|
||||
- cd ../..
|
||||
|
||||
deploy:
|
||||
provider: s3
|
||||
access_key_id: $S3_ACCESS_KEY
|
||||
secret_access_key: $S3_SECRET_KEY
|
||||
bucket: $S3_BUCKET
|
||||
skip_cleanup: true
|
||||
local_dir: swig/bin
|
||||
on:
|
||||
all_branches: true
|
4
frostwire-jlibtorrent/CHANGE_VERSION_CHECKLIST.txt
Normal file
4
frostwire-jlibtorrent/CHANGE_VERSION_CHECKLIST.txt
Normal file
@ -0,0 +1,4 @@
|
||||
bump version numberc in:
|
||||
- build.gradle 'version', then run swig/run-swig.sh, then build-xxx.sh and gradle build
|
||||
- changelog.txt
|
||||
|
1
frostwire-jlibtorrent/FUNDING.yml
Normal file
1
frostwire-jlibtorrent/FUNDING.yml
Normal file
@ -0,0 +1 @@
|
||||
github: gubatron
|
23
frostwire-jlibtorrent/LICENSE.md
Normal file
23
frostwire-jlibtorrent/LICENSE.md
Normal file
@ -0,0 +1,23 @@
|
||||
The MIT License
|
||||
===============
|
||||
|
||||
Copyright (C) 2016 FrostWire, LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject
|
||||
to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
131
frostwire-jlibtorrent/README.md
Normal file
131
frostwire-jlibtorrent/README.md
Normal file
@ -0,0 +1,131 @@
|
||||
frostwire-jlibtorrent
|
||||
=====================
|
||||

|
||||
|
||||
A swig Java interface for libtorrent by the makers of FrostWire.
|
||||
|
||||
Develop libtorrent based apps with the joy of coding in Java.
|
||||
|
||||
[Discord Developer Chatroom](https://discord.com/channels/461752211802947585/461766946669461515)
|
||||
|
||||
Using
|
||||
========
|
||||
|
||||
[Download the latest release .jars](https://github.com/frostwire/frostwire-jlibtorrent/releases)
|
||||
|
||||
All platforms will need you to use at least 2 `.jar` files.
|
||||
|
||||
The `.jar` with the java classes -> `jlibtorrent-w.x.y.z.jar` and a secondary `.jar`s containing the JNI binary library for the particular OS and CPU architecture.
|
||||
|
||||
In the case of desktop operating systems, you might want to extract the shared library inside the jar (.dll, .so, .dylib) and place it in a folder specified by the `java.library.path`
|
||||
|
||||
The secondary jars are:
|
||||
- Windows: `jlibtorrent-windows-w.x.y.z.jar` (x86 and x86_64 .dlls)
|
||||
- Mac: `jlibtorrent-macosx-w.x.y.z.jar` (x86_64 .dylib)
|
||||
- Linux: `jlibtorrent-linux-w.x.y.z.jar` (x86 and x86_64 .so)
|
||||
|
||||
In the case of Android, make sure to put the following 3 jars in your project's `libs` folder (see [FrostWire for Android's](https://github.com/frostwire/frostwire/tree/master/android/libs) as an example):
|
||||
- `jlibtorrent-w.x.y.z.jar`
|
||||
- `jlibtorrent-android-arm-w.x.y.z.jar`
|
||||
- `jlibtorrent-android-x86-w.x.y.z.jar`
|
||||
|
||||
If you use ProGuard to obfuscate/minify make sure to add the following statement
|
||||
|
||||
`-keep class com.frostwire.jlibtorrent.swig.libtorrent_jni {*;}`
|
||||
|
||||
|
||||
Note that there are multiple version of jlibtorrent for different platforms: `jlibtorrent`, `jlibtorrent-windows`, `jlibtorrent-linux`, `jlibtorrent-macosx` and `jlibtorrent-android-<arch>`. These are all different artifacts.
|
||||
|
||||
For examples look at https://github.com/frostwire/frostwire-jlibtorrent/tree/master/src/test/java/com/frostwire/jlibtorrent/demo
|
||||
|
||||
Architectures supported:
|
||||
|
||||
- Android (armeabi-v7a, arm64-v8a, x86, x86_64)
|
||||
- Linux (x86, x86_64)
|
||||
- Windows (x86, x86_64)
|
||||
- Mac OS X (x86_64)
|
||||
|
||||
Building with Travis
|
||||
====================
|
||||
|
||||
You need:
|
||||
|
||||
- Setup a travis account at http://travis-ci.org and get familiar with
|
||||
the service if necessary.
|
||||
- Open an account with Amazon Web Services (AWS) and get familiar with
|
||||
S3 (for storage) and IAM (for users).
|
||||
- Some familiarity with `git` commands.
|
||||
|
||||
The process is:
|
||||
|
||||
- Create a user in amazon IAM, let's suppose it is `user1`. Download
|
||||
credentials for the keys.
|
||||
- Create a bucket in amazon S3, let's suppose it is `jlibtorrent1`.
|
||||
- Set the permission of the bucket according to your workflow, but at
|
||||
least the `user1` should have permission to put/upload to the bucket.
|
||||
See for example this bucket policy:
|
||||
```json
|
||||
{
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS":"arn:aws:iam::<user1's ARN here>:user/user1"},
|
||||
"Action": "s3:PutObject",
|
||||
"Resource": "arn:aws:s3:::jlibtorrent1/*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": "*",
|
||||
"Action": "s3:GetObject",
|
||||
"Resource": "arn:aws:s3:::jlibtorrent1/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- Fork the project in github.
|
||||
- Go to travis and enable the repository.
|
||||
- Go to 'More options' > 'Settings' > 'Environment Variables' and set the
|
||||
`S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET` variables using the values in the
|
||||
credentials file for the user you created and the bucket name you created.
|
||||
- Clone locally your repo, let's assume to the `jlibtorrent` folder and
|
||||
checkout the stable branch:
|
||||
```bash
|
||||
$ git clone <your fork repo url> jlibtorrent
|
||||
$ cd jlibtorrent
|
||||
$ git checkout master
|
||||
```
|
||||
- Verify in your travis online if the build already started. The build
|
||||
could take about 40 minutes, be patient.
|
||||
- When finished, check your s3 bucket for the binaries.
|
||||
- To trigger a new build, just make a change or merge new changes from
|
||||
the stable branch, commit and push.
|
||||
|
||||
Building Locally (Mac and Linux)
|
||||
================================
|
||||
Building on Travis is something recommended only once you know you're done with your work as builds can take above 30 minutes to finish for all platforms and architectures.
|
||||
|
||||
When you're developing and debugging you need faster builds, and these can be performed locally with the help of build scripts in the `swig/` folder.
|
||||
|
||||
Thre's a build script for each operating system, for example if you're on macos you can use the `build-macos.sh`, running it without setting things up should tell you about certain environment variables you'll need to set up. To understand the build process we recommend you read your corresponding build script and [`build-utils.shinc`](https://github.com/frostwire/frostwire-jlibtorrent/blob/master/swig/build-utils.shinc)
|
||||
|
||||
The hacking and building process might require you to run the `run-swig.sh` script, we usually need to run this script if there are C++ api changes in libtorrent that require adjustments in `libtorrent.i` or `libtorrent.h`, this script will create automatic JNI wrappers in the outer source java folders. You should not run this script unless you know what you're doing.
|
||||
|
||||
Then you run the build script for your OS, get to the parent folder and invoke
|
||||
`gradle build`
|
||||
|
||||
The gradle build will look in the swig folder for the JNI shared libraries and it will automatically package the native holding `.jar` files, the final jars will be in the `build/libs` folder.
|
||||
|
||||
The windows build script is not finished, for now the windows build is done with travis builds.
|
||||
|
||||
Projects using jLibtorrent
|
||||
==========================
|
||||
- [FrostWire](http://www.frostwire.com) (both desktop and android editions)
|
||||
- [TorrentStream-Android](https://github.com/mianharisali/TorrentStream-Android)
|
||||
- [Simple-Torrent-Android](https://github.com/masterwok/simple-torrent-android)
|
||||
- [TorrentTunes-Client](https://github.com/dessalines/torrenttunes-client)
|
||||
- [LibreTorrent](https://github.com/proninyaroslav/libretorrent)
|
||||
|
||||
License
|
||||
========
|
||||
|
||||
This software is offered under the MIT License, available [here](License.md).
|
234
frostwire-jlibtorrent/build.gradle
Normal file
234
frostwire-jlibtorrent/build.gradle
Normal file
@ -0,0 +1,234 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'signing'
|
||||
apply plugin: 'maven'
|
||||
|
||||
group 'com.frostwire'
|
||||
archivesBaseName = 'jlibtorrent'
|
||||
// Just changing version here should be all that's necessary to bump the version on the library
|
||||
version '1.2.12.0'
|
||||
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
|
||||
if (!hasProperty('ossrhUsername')) {
|
||||
ext.ossrhUsername = ''
|
||||
}
|
||||
|
||||
if (!hasProperty('ossrhPassword')) {
|
||||
ext.ossrhPassword = ''
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
task javadoc2(type: Javadoc) {
|
||||
source = sourceSets.main.allJava
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc2) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc2.destinationDir
|
||||
}
|
||||
|
||||
task nativeMacOSXJar(type: Zip) {
|
||||
destinationDir file("$buildDir/libs")
|
||||
baseName 'jlibtorrent-macosx'
|
||||
extension 'jar'
|
||||
from fileTree(dir: 'swig/bin/release/macosx', excludes: ['**/ed25519', '**/src'], include: '**/*libjlibtorrent.dylib')
|
||||
into 'lib/'
|
||||
rename(".dylib", "-${version}.dylib")
|
||||
}
|
||||
|
||||
task nativeWindowsJar(type: Zip) {
|
||||
destinationDir file("$buildDir/libs")
|
||||
baseName 'jlibtorrent-windows'
|
||||
extension 'jar'
|
||||
from fileTree(dir: 'swig/bin/release/windows', excludes: ['**/ed25519', '**/src'], include: '**/*jlibtorrent.dll')
|
||||
into 'lib/'
|
||||
rename(".dll", "-${version}.dll")
|
||||
}
|
||||
|
||||
task nativeLinuxJar(type: Zip) {
|
||||
destinationDir file("$buildDir/libs")
|
||||
baseName 'jlibtorrent-linux'
|
||||
extension 'jar'
|
||||
from fileTree(dir: 'swig/bin/release/linux', excludes: ['**/ed25519', '**/src'], include: '**/*libjlibtorrent.so')
|
||||
into 'lib/'
|
||||
rename(".so", "-${version}.so")
|
||||
}
|
||||
|
||||
task nativeAndroidArmJar(type: Zip) {
|
||||
destinationDir file("$buildDir/libs")
|
||||
baseName 'jlibtorrent-android-arm'
|
||||
extension 'jar'
|
||||
from fileTree(dir: 'swig/bin/release/android', include: 'armeabi-v7a/libjlibtorrent.so')
|
||||
into 'lib/'
|
||||
rename(".so", "-${version}.so")
|
||||
}
|
||||
|
||||
task nativeAndroidX86Jar(type: Zip) {
|
||||
destinationDir file("$buildDir/libs")
|
||||
baseName 'jlibtorrent-android-x86'
|
||||
extension 'jar'
|
||||
from fileTree(dir: 'swig/bin/release/android', include: 'x86/libjlibtorrent.so')
|
||||
into 'lib/'
|
||||
rename(".so", "-${version}.so")
|
||||
}
|
||||
|
||||
task nativeAndroidArm64Jar(type: Zip) {
|
||||
destinationDir file("$buildDir/libs")
|
||||
baseName 'jlibtorrent-android-arm64'
|
||||
extension 'jar'
|
||||
from fileTree(dir: 'swig/bin/release/android', include: 'arm64-v8a/libjlibtorrent.so')
|
||||
into 'lib/'
|
||||
rename(".so", "-${version}.so")
|
||||
}
|
||||
|
||||
task nativeAndroidX64Jar(type: Zip) {
|
||||
destinationDir file("$buildDir/libs")
|
||||
baseName 'jlibtorrent-android-x86_64'
|
||||
extension 'jar'
|
||||
from fileTree(dir: 'swig/bin/release/android', include: 'x86_64/libjlibtorrent.so')
|
||||
into 'lib/'
|
||||
rename(".so", "-${version}.so")
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
archives nativeMacOSXJar
|
||||
archives nativeWindowsJar
|
||||
archives nativeLinuxJar
|
||||
archives nativeAndroidArmJar
|
||||
archives nativeAndroidX86Jar
|
||||
archives nativeAndroidArm64Jar
|
||||
archives nativeAndroidX64Jar
|
||||
}
|
||||
|
||||
signing {
|
||||
required { gradle.taskGraph.hasTask("uploadArchives") }
|
||||
sign configurations.archives
|
||||
}
|
||||
|
||||
uploadArchives {
|
||||
repositories {
|
||||
mavenDeployer {
|
||||
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
|
||||
|
||||
addFilter('jlibtorrent') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent'
|
||||
}
|
||||
addFilter('jlibtorrent-macosx') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent-macosx'
|
||||
}
|
||||
addFilter('jlibtorrent-windows') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent-windows'
|
||||
}
|
||||
addFilter('jlibtorrent-linux') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent-linux'
|
||||
}
|
||||
addFilter('jlibtorrent-android-arm') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent-android-arm'
|
||||
}
|
||||
addFilter('jlibtorrent-android-x86') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent-android-x86'
|
||||
}
|
||||
addFilter('jlibtorrent-android-arm64') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent-android-arm64'
|
||||
}
|
||||
addFilter('jlibtorrent-android-x86_64') { artifact, file ->
|
||||
artifact.name == 'jlibtorrent-android-x86_64'
|
||||
}
|
||||
|
||||
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
|
||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
||||
}
|
||||
|
||||
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
|
||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
||||
}
|
||||
|
||||
pom('jlibtorrent').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
}
|
||||
pom('jlibtorrent-macosx').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
addDependency(asNode())
|
||||
|
||||
}
|
||||
pom('jlibtorrent-windows').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
addDependency(asNode())
|
||||
}
|
||||
pom('jlibtorrent-linux').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
addDependency(asNode())
|
||||
}
|
||||
pom('jlibtorrent-android-arm').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
addDependency(asNode())
|
||||
}
|
||||
pom('jlibtorrent-android-x86').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
addDependency(asNode())
|
||||
}
|
||||
pom('jlibtorrent-android-arm64').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
addDependency(asNode())
|
||||
}
|
||||
pom('jlibtorrent-android-x86_64').withXml {
|
||||
asNode().children().last() + pomData()
|
||||
addDependency(asNode())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def pomData() {
|
||||
return {
|
||||
resolveStrategy = Closure.DELEGATE_FIRST
|
||||
name 'frostwire-jlibtorrent'
|
||||
description 'A swig Java interface for libtorrent by the makers of FrostWire.'
|
||||
url 'https://github.com/frostwire/frostwire-jlibtorrent'
|
||||
scm {
|
||||
connection 'scm:git:git://github.com/frostwire/frostwire-jlibtorrent.git'
|
||||
developerConnection 'scm:git:ssh:git@github.com/frostwire/frostwire-jlibtorrent.git'
|
||||
url 'https://github.com/frostwire/frostwire-jlibtorrent'
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name 'The MIT License'
|
||||
url 'https://github.com/frostwire/frostwire-jlibtorrent/blob/master/LICENSE.md'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id 'gubatron'
|
||||
name 'Angel Leon'
|
||||
email 'gubatron@gmail.com'
|
||||
}
|
||||
developer {
|
||||
id 'aldenml'
|
||||
name 'Alden Torres'
|
||||
email 'aldenml@gmail.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def addDependency(root) {
|
||||
def dependenciesNode = root.dependencies[0]
|
||||
if (!dependenciesNode) {
|
||||
dependenciesNode = root.appendNode('dependencies')
|
||||
}
|
||||
def depNode = dependenciesNode.appendNode('dependency')
|
||||
depNode.appendNode('groupId', group)
|
||||
depNode.appendNode('artifactId', archivesBaseName)
|
||||
depNode.appendNode('version', version)
|
||||
}
|
682
frostwire-jlibtorrent/changelog.txt
Normal file
682
frostwire-jlibtorrent/changelog.txt
Normal file
@ -0,0 +1,682 @@
|
||||
1.2.12.0
|
||||
* lt:fix loading of DHT node ID from previous session on startup
|
||||
* lt:use getrandom(), when available, and fall back to /dev/urandom
|
||||
* lt:fix uTP issue acking FIN packets
|
||||
* lt:validate HTTPS certificates by default (trackers and web seeds)
|
||||
* lt:load SSL certificates from windows system certificate store, to authenticate trackers
|
||||
* lt:introduce mitigation for Server Side Request Forgery in tracker and web seed URLs
|
||||
* lt:fix error handling for pool allocation failure
|
||||
|
||||
1.2.11.0
|
||||
* New TorrentHandle.inSession() [blocking method, do not use in main UI threads]
|
||||
* New ability for SessionManager to build a paused session
|
||||
* New SessionManager.start(SessionParams, session_flags_t)
|
||||
* New SessionHandle.PAUSED (session_flag_t) and corresponding unit tests
|
||||
* Removed TorrentStatus::State::ALLOCATING enum
|
||||
* Removed torrent_status.state_t.unused_enum_for_backwards_compatibility_allocating
|
||||
* libtorrent 1.2.11.0 update (471e772cb7038f1bf5f44c32a09eb42fbb80ee99)
|
||||
* lt: upgraded to openssl 1.1.1l
|
||||
* lt: fix issue with moving the session object
|
||||
* lt: deprecate torrent_status::allocating. This state is no longer used
|
||||
* lt: fix bug creating torrents with symbolic links
|
||||
* lt: remove special case to save metadata in resume data unconditionally when added throught magnet link
|
||||
* lt: fix bugs in mutable-torrent support (reusing identical files from different torrents)
|
||||
* lt: fix incorrectly inlined move-assignment of file_storage
|
||||
* lt: add session::paused flag, and the ability to construct a session in paused mode
|
||||
* lt: fix session-pause causing tracker announces to fail
|
||||
* lt: fix peer-exchange flags bug
|
||||
* lt: allow saving resume data before metadata has been downloaded (for magnet links)
|
||||
* lt: record blocks in the disk queue as downloaded in the resume data
|
||||
* lt: fix bug in set_piece_deadline() when set in a zero-priority piece
|
||||
* lt: fix issue in URL parser, causing issues with certain tracker URLs
|
||||
* lt: use a different error code than host-unreachable, when skipping tracker announces
|
||||
|
||||
1.2.10.0
|
||||
* libtorrent 1.2.10 update (70f1de3f7ec4012aaea420ff150ef0135d397706)
|
||||
* lt: improve stat_file() performance on Windows
|
||||
* lt: fix issue with loading invalid torrents with only 0-sized files
|
||||
* lt: fix to avoid large stack allocations
|
||||
* lt: add macro TORRENT_CXX11_ABI for clients building with C++14 against
|
||||
libtorrent build with C++11
|
||||
* lt: removed deprecated wstring overloads on non-windows systems
|
||||
* lt: drop dependency on Unicode's ConvertUTF code (which had a license
|
||||
incompatible with Debian)
|
||||
* lt: fix bugs exposed on big-endian systems
|
||||
* lt: fix detection of hard-links not being supported by filesystem
|
||||
* lt: fixed resume data regression for seeds with prio 0 files
|
||||
* binaries: compiler upgraded from g++-5 to g++-7
|
||||
|
||||
1.2.8.0
|
||||
* add router.utorrent.com to list of DHT bootstrap nodes
|
||||
* Android builds with new NDK r21d
|
||||
* lt: validate UTF-8 encoding of client version strings from peers
|
||||
* lt: don't time out tracker announces as eagerly while resolving hostnames
|
||||
* lt: fix NAT-PMP shutdown issue
|
||||
* lt: improve hostname lookup by merging identical lookups
|
||||
* lt: fix network route enumeration for large routing tables
|
||||
* lt: fixed issue where pop_alerts() could return old, invalid alerts
|
||||
* lt: fix issue when receiving have-all message before the metadata
|
||||
* lt: don't leave lingering part files handles open
|
||||
* lt: disallow calling add_piece() during checking
|
||||
* lt: fix incorrect filename truncation at multi-byte character
|
||||
* lt: always announce listen port 1 when using a proxy
|
||||
|
||||
1.2.7.0
|
||||
* libtorrent 1.2.7 update (ce0a85f783bec37484776a37fe3662279091ecc5)
|
||||
* upgraded to boost 1.73.0
|
||||
* upgraded to openssl 1.1.1g
|
||||
* lt: fix incorrect filename truncation at multi-byte character
|
||||
* lt: always announce listen port 1 when using a proxy
|
||||
* lt: add set_alert_fd in python binding, to supersede set_alert_notify
|
||||
* lt: fix bug in part files > 2 GiB
|
||||
* lt: add function to clear the peer list for a torrent
|
||||
* lt: fix resume data functions to save/restore more torrent flags
|
||||
* lt: limit number of concurrent HTTP announces
|
||||
* lt: fix queue position for force_rechecking a torrent that is not auto-managed
|
||||
* lt: improve rate-based choker documentation, and minor tweak
|
||||
* lt: undeprecate upnp_ignore_nonrouters (but refering to devices on our subnet)
|
||||
* lt: increase default tracker timeout
|
||||
* lt: retry failed socks5 server connections
|
||||
* lt: allow UPnP lease duration to be changed after device discovery
|
||||
* lt: fix IPv6 address change detection on Windows
|
||||
* lt: fix peer timeout logic
|
||||
* lt: simplify proxy handling. A proxy now overrides listen_interfaces
|
||||
* lt: fix issues when configured to use a non-default choking algorithm
|
||||
* lt: fix issue in reading resume data
|
||||
* lt: revert NXDOMAIN change from 1.2.4
|
||||
* lt: don't open any listen sockets if listen_interfaces is empty or misconfigured
|
||||
* lt: fix bug in auto disk cache size logic
|
||||
* lt: fix issue with outgoing_interfaces setting, where bind() would be called twice
|
||||
* lt: add build option to disable share-mode
|
||||
* lt: support validation of HTTPS trackers
|
||||
* lt: deprecate strict super seeding mode
|
||||
* lt: make UPnP port-mapping lease duration configurable
|
||||
* lt: deprecate the bittyrant choking algorithm
|
||||
* lt: add build option to disable streaming
|
||||
|
||||
1.2.6.0
|
||||
* There was no 1.2.6 release, we waited and jumped all the way to 1.2.7.0
|
||||
|
||||
1.2.5.0
|
||||
* libtorrent 1.2.5 update (0f337b9ce7a1b0fc87f48843933b1c5c4dd5a9ec)
|
||||
* New SettingsPack::dhtUploadRate(), SettingsPack::dhtUploadRate(int)
|
||||
* lt:announce port=1 instead of port=0, when there is no listen port
|
||||
* lt:fix LSD over IPv6
|
||||
* lt:support TCP_NOTSENT_LOWAT on Linux
|
||||
* lt:fix correct interface binding of local service discovery multicast
|
||||
* lt:fix issue with knowing which interfaces to announce to trackers and DHT
|
||||
* lt:undeprecate settings_pack::dht_upload_rate_limit
|
||||
|
||||
1.2.4.0
|
||||
* libtorrent 1.2.4 update (ad83b1c0eb293b63c69f7879ca6ba2381369f77f)
|
||||
* Java source compatibility upgraded from 1.7 to 1.8
|
||||
* lt:fix binding TCP and UDP sockets to the same port, when specifying port 0
|
||||
* lt:fix announce_to_all_trackers and announce_to_all_tiers behavior
|
||||
* lt:fix suggest_read_cache setting
|
||||
* lt:back-off tracker hostname looksups resulting in NXDOMAIN
|
||||
* lt:lower SOCKS5 UDP keepalive timeout
|
||||
* lt:fix external IP voting for multi-homed DHT nodes
|
||||
* lt:deprecate broadcast_lsd setting. Just use multicast
|
||||
* lt:deprecate upnp_ignore_nonrouters setting
|
||||
* lt:don't attempt sending event=stopped if event=start never succeeded
|
||||
* lt:make sure &key= stays consistent between different source IPs (as mandated by BEP7)
|
||||
* lt:fix binding sockets to outgoing interface
|
||||
* lt:add new socks5_alert to trouble shoot SOCKS5 proxies
|
||||
|
||||
1.2.3.0
|
||||
* libtorrent 1.2.3 update (b5bf6c3260bd726b0181671625c007be5ad49845)
|
||||
* using android NDK r20b
|
||||
* upgraded to boost 1.72.0
|
||||
* upgraded to openssl 1.1.1d
|
||||
* swig version 4.0.1 (from 3.0.12)
|
||||
* lt:fix erroneous event=completed tracker announce when checking files
|
||||
* lt:promote errors in parsing listen_interfaces to post listen_failed_alert
|
||||
* lt:fix bug in protocol encryption/obfuscation
|
||||
* lt:fix buffer overflow in SOCKS5 UDP logic
|
||||
* lt:fix issue of rapid calls to file_priority() clobbering each other
|
||||
* lt:clear tracker errors on success
|
||||
* lt:optimize setting with unlimited unchoke slots
|
||||
* lt:fixed restoring of trackers, comment, creation date and created-by in resume data
|
||||
* lt:fix handling of torrents with too large pieces
|
||||
* lt:fixed division by zero in anti-leech choker
|
||||
* lt:fixed bug in torrent_info::swap
|
||||
|
||||
1.2.2.0
|
||||
* libtorrent 1.2.2 update (d5b56ca1876dc7b96ef9aac7c7584e1f61d25774)
|
||||
* using android NDK r20 (released June 2019)
|
||||
* new local build script for android x86_64 (api level 20)
|
||||
* updated to boost 1.71.0
|
||||
* upgraded openssl to 1.1.1c
|
||||
* compiled with std=c++14 (up from c++11)
|
||||
* lt:pick contiguous pieces from peers with high download rate
|
||||
* lt:fix error handling of moving storage to a drive letter that isn't mounted
|
||||
* lt:fix integer overflow in http parser
|
||||
* lt:fix integer overflow in chunked http parser
|
||||
* lt:factor out and unit test parts of the DHT routing table logic
|
||||
* lt:improve sanitation of symlinks, to support more complex link targets (file_storage::sanitize_symlinks)
|
||||
* lt:avoid empty dht routing table buckets
|
||||
* lt:fix dht_stats_alert routing table stats for multi-homed clients
|
||||
* lt:fix entry assignment from bdecode_node and lazy_entry
|
||||
* lt:fix use-after-free issue in socket_type
|
||||
* lt:fix error code messages when building without deprecated functions
|
||||
* lt:feature to disable DHT, PEX and LSD per torrent
|
||||
* lt:fix seeding of random number generator on mingw
|
||||
* lt:fix issue where trackers from magnet links were not included in create_torrent()
|
||||
* lt:extend the whole_pieces_threshold setting to also request contiguous pieces from fast peers
|
||||
* lt:fix error handling of moving storage to a drive letter that isn't mounted
|
||||
* lt:don't leak exceptions out of handler callbacks in resolver
|
||||
* lt:ensure headers build independently
|
||||
* lt:fix unit template's mutating operators to give them proper ref qualifiers
|
||||
|
||||
1.2.1.0
|
||||
* using android NDK r19c (released January 2019)
|
||||
* updated to boost 1.70.0
|
||||
* lt:make sure session cleanup releases all its references to torrents
|
||||
* lt:track the mapped port for each NAT mapping transport
|
||||
* lt:optimize resolve_duplicate_filenames_slow()
|
||||
* lt:use a more restrictive limit on number of pieces allowed in a torrent
|
||||
* lt:improve disk I/O logging
|
||||
* lt:always flush disk I/O job queue before shutdown
|
||||
* lt:fix typo in validation of reject messages. Make on_choke a bit more defensive
|
||||
* lt:tighten up validation of dont-have messages
|
||||
* lt:tighten up validation of reject messages, to ensure consistency of stats counters
|
||||
* lt:tighten up message size checks
|
||||
* lt:update symlinks to conform to BEP 47
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.20
|
||||
* updated to libtorrent RC_1_2 HEAD
|
||||
* updated to OpenSSL 1.1.1b
|
||||
* lt:add support for creating symlinks, for torrents with symlinks in them
|
||||
* lt:allow padfiles of equal size to share the same filename
|
||||
* lt:fix seed_mode flag
|
||||
* lt:support numeric suffixes to magnet link parameter names
|
||||
* lt:added FrostWire's client ID
|
||||
* lt:don't try to hash empty read in do_uncached_hash
|
||||
* lt:don't copy a vector into the async_write operation for iovec
|
||||
* lt:on linux, link against lbdl when using openssl
|
||||
* lt:use UNC paths pervasively on windows
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.19
|
||||
* libtorrent release 1.2.0
|
||||
* using android NDK r18b
|
||||
* updated to boost 1.69.0
|
||||
* updated to OpenSSL 1.1.1a
|
||||
* fixed activeDhtLimit SettingsPack setter
|
||||
* development: new local build scripts for macosx, android, linux, windows
|
||||
* lt:only allow cwnd to be reduced so often (utp)
|
||||
* lt:avoid announcing local ip to private tracker
|
||||
* lt:don't treat loss of MTU probe packet as a congestion signal (utp)
|
||||
* lt:make sure we reset the duplicate ack counter every time we don't receive
|
||||
a duplicate ack (utp)
|
||||
* lt:remove old (incompatible) sequence number build option (utp)
|
||||
* lt:don't leave slow-start just because we hit the advertized receive window
|
||||
* lt:simplify and improve the uTP deferred ACK logic to respond earlier
|
||||
* lt:improve logic for fast-retransmitting packets on incoming SACK
|
||||
* lt:improve utp verbose logging a bit and make the parser pull out more
|
||||
metrics
|
||||
* lt:restore permissions on directories to 1.1. i.e. rely on umask
|
||||
* lt:minor fix to invalid_request_alert logging
|
||||
* lt:add assignment operator to span
|
||||
* lt:fix %u -> %d format codes
|
||||
* lt:fix deprecation markup in torrent_status
|
||||
* lt:tweak heuristic of how to interpret url seeds in multi-file torrents
|
||||
* lt:added more TORRENT_DEPRECATED_ENUM and deprecated unused aio_max in
|
||||
settings_pack
|
||||
* lt:take a string_view in setting_by_name
|
||||
* lt:fix typo in peer log
|
||||
* lt:deprecate start_default_features flag, it's only used in deprecated API
|
||||
* lt:remove verbose peer logging
|
||||
* lt:make stack_allocator::format_string() grow the buffer for large strings
|
||||
* lt:move where socket buffers are set up, to happen after the socket is
|
||||
opened. log errors in the peer's log instead of session and torrent
|
||||
* lt:add stats counter for the number of outstanding async_accept calls
|
||||
* lt:fix potential issue where the dht port message is sent before the peer
|
||||
handshake
|
||||
* lt:correct %u format code for pieces in printf() calls
|
||||
* lt:attempt to fix an assert for a newly connected peer that disconnects just
|
||||
as we receive the metadata
|
||||
* lt:don't use page aligned disk buffers
|
||||
* lt:include &ipv4= for private trackers
|
||||
* lt:add support for &ipv4= tracker argument
|
||||
* lt:use new bdecoder in ut_metadata class
|
||||
* lt:fix redundant bytes overflow
|
||||
* lt:check for self-assignment in bitfield operator=
|
||||
* lt:initialize bencoded ints with zero when constructed
|
||||
* lt:exit natpmp::on_reply earlier if we're shutting down
|
||||
* lt:fix setting ipv6 interface
|
||||
* lt:a better fix to the ssl port announce bug
|
||||
* lt:perfect forward async handlers for udp_socket to underlying asio socket
|
||||
* lt:move the whole add_torrent_params object into save_resume_data_alert
|
||||
* lt:actually fix the issue with the second tracker announce with port 0
|
||||
* lt:remove redundant MTU boundary checks
|
||||
* lt:fix of asio-debugging build in natpmp. resend_request could be called
|
||||
directly, not only as a handler for an async operation
|
||||
* lt:fix move_storage with save_path with a trailing slash
|
||||
* lt:only make snubbed peers invert the piece picking strategy when we're
|
||||
doing rarest first
|
||||
* lt:ssl listen port fix and improved tracker announce logging
|
||||
* lt:properly tear down the disk_io_thread object in set_piece_hashes() when
|
||||
exiting via an exception
|
||||
* lt:make throwing versions of read_resume_data
|
||||
* lt:fix overflow in sliding_average in the case of very high download rates
|
||||
* lt:renamed debug_notification to connect_notification
|
||||
* lt:fix issue in udp_socket with unusual socket failure
|
||||
* lt:utp close-reason use after free fix
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
|
||||
1.2.0.18
|
||||
|
||||
* using android NDK r17c
|
||||
* updated to boost 1.68.0
|
||||
* updated to OpenSSL 1.1.1
|
||||
* expose to java SWIG api the aux::arm_neon_support flag for android
|
||||
runtime verification
|
||||
* removed hack of custom getauxval definition
|
||||
* removed hack of fgetpos fsetpos
|
||||
* removed hack of mulodi4
|
||||
* null check protection in EnumNet
|
||||
* added new constructor to TorrentInfo from byte array
|
||||
* improved API for AnnounceEntry
|
||||
* added AnnounceEndpoint java API, it is a lightweight class
|
||||
* convert ErrorCode to a lightweight class
|
||||
* convert PeerInfo to a lightweight class, client is UTF-8 decoded
|
||||
* Vectors.byte_vector2string supports only ASCII and UTF-8
|
||||
* lt:fix redundant flushes of partfile metadata
|
||||
* lt:fix overflow in calc_bytes(), fix bug in piece picker accounting of
|
||||
filtered pad blocks
|
||||
* lt:improve type-safety of the severity parameter to
|
||||
peer_connection::disconnect()
|
||||
* lt:fix seed count when attaching a peer is aborted
|
||||
* lt:add option to ignore min-interval from tracker, when
|
||||
force-reannouncing a tracker
|
||||
* lt:raise default value for active_limit to 500, since it's supposed to be
|
||||
an upper sanity check limit
|
||||
* lt:make the print function for entry actually be json-like
|
||||
* lt:fall back to copy+remove if rename_file fails
|
||||
* lt:improve handling of filesystems not supporting fallocate()
|
||||
* lt:improve piece picker performance in tracking pad-blocks
|
||||
* lt:force proxy no longer disables the DHT
|
||||
* lt:simplify total_have/have_want/total_want. Make piece_picker track pad
|
||||
blocks and compute byte-progress at block granularity
|
||||
* lt:fix issue in self-connection detection introduced with the change to
|
||||
generate unique peer-ids for each connection.
|
||||
* lt:fix exporting files to avoid overwriting existing files, before
|
||||
exporting anything from a parts file, check whether it contains valid data
|
||||
* lt:improve connect-boost feature, to make new torrents quickly connect
|
||||
peers
|
||||
* lt:add a few more stats counters measuring outgoing connection attempts.
|
||||
simplify session_stats_header_alert by posting it on first call to
|
||||
post_session_stats() instead of making it gated by the alert_mask
|
||||
* lt:remove dead code from piece picker
|
||||
* lt:tweak the auto-cache-size logic to have slightly smaller cache
|
||||
* lt:fix deprecation of mmap_cache
|
||||
* lt:add missing increment of on_disk_counter and num_blocks_hashed counters
|
||||
* lt:apply piece priorities immediately, even though file priority updates
|
||||
are async. save both file- and piece priorities in fast resume
|
||||
* lt:simplify natpmp gateway and local address discovery
|
||||
* lt:fix typo in #if tests for TORRENT_DISABLE_ENCRYPTION
|
||||
* lt:in torrent_handle::id(), only shift down the pointer by 10 bits
|
||||
* lt:add piece index range checks on have_piece() and read_piece()
|
||||
* lt:make metric_type_t an enum class, deprecate the in-class enum values
|
||||
* lt:only post alerts for newly opened listen sockets, and only attempt to
|
||||
map ports for newly opened sockets
|
||||
* lt:remove special handling of uTP peers, uTP connections are no longer
|
||||
exempt from rate limits by default
|
||||
* lt:make natpmp deal with address_v6 instead of the bytes_type, and use
|
||||
write_address instead of memcpy
|
||||
* lt:fix exporting files from partfile while seeding
|
||||
* lt:set port in handshake based on source address
|
||||
* lt:fix Windows "file::preadv" emulation EOF handling
|
||||
* lt:fix windows async read EOF handling
|
||||
* lt:fix bug in read/write resume data functions
|
||||
* lt:dht don't set implied_port for SSL torrents
|
||||
* lt:dht announce with per-interface listen port
|
||||
* lt:deprecate (and disable) the force-proxy setting. Instead, always use
|
||||
the proxy when set, never fall back on circumventing it
|
||||
* lt:move the file priority vector throught the disk_io_job, to avoid copies
|
||||
* lt:raise priority of cache_flushed_alert and post it unconditionally when
|
||||
triggered explicitly by the client
|
||||
* lt:fix some validation issues in read_resume_data()
|
||||
* lt:bump the minimum number of hash jobs per thread from 2 to 4
|
||||
* lt:fix deadlock when loading libtorrent Dll
|
||||
* lt:fix torrent files prioritization
|
||||
* lt:fix some unintentional copies (with explicit moves)
|
||||
* lt:qualify some assignment operators to disallow assignment to temporaries
|
||||
* lt:fix missing move of file object in part_file
|
||||
* lt:use more threads when creating torrents
|
||||
* lt:bump checking_mem_usage default setting
|
||||
* lt:enable coalesce_reads and coalesce_writes by default on windows
|
||||
* lt:set the minimum number of checking jobs based on the number of hasher
|
||||
threads
|
||||
* lt:fix coalesce read bug
|
||||
* lt:introduce a fast-path for the hash disk job
|
||||
* lt:fix race condition in part_file
|
||||
* lt:fix parts file i/o errors
|
||||
* lt:fixed sign implicit conversion warnings and loop logic in new enum_net
|
||||
code
|
||||
* lt:improve the API for iterating over all files and pieces, with the new
|
||||
strong index types
|
||||
* lt:deprecate network-threads setting
|
||||
* lt:scrape_reply_alert should be high priority, since it's triggered by the
|
||||
client
|
||||
* lt:add support for multi-home NAT-PMP and Port Control Protocol (PCP)
|
||||
* lt:report transport version in NAT-PMP send/receive logs
|
||||
* lt:return a vector of mapping ids from add_port_mapping
|
||||
* lt:don't re-map all listen sockets when changing listen_interfaces
|
||||
* lt:create a natpmp instance for each listen socket
|
||||
* lt:enum_routes fixes on Linux
|
||||
* lt:fix scope_id in enum_routes on Windows
|
||||
* lt:remove UNC prefixes from device names on Windows
|
||||
* lt:fix netmask of routes on Windows
|
||||
* lt:deliver notification of alerts being dropped via alerts_dropped_alert
|
||||
* lt:raise priority of fastresume_rejected_alert
|
||||
* lt:don't use the partfile for existing files when their priority is 0
|
||||
* lt:set the hidden attribute when creating the part file
|
||||
* lt:only start a new accept request on new listen sockets, fix mapping new
|
||||
listen sockets when re-mapping is not requested
|
||||
* lt:don't change state to downloading if the torrent is finished
|
||||
* lt:bump `file_error_alert` priority
|
||||
* lt:fix address of point-to-point interfaces
|
||||
* lt:don't enable reuse-address for UDP sockets, as it will always succeed
|
||||
and not get any incoming packets
|
||||
* lt:account for partially downloaded pieces when announcing as a seed
|
||||
* lt:fix bandwidth allocation
|
||||
* lt:don't attempt to make uTP connections if we don't have any outgoing UDP
|
||||
sockets
|
||||
* lt:fix empty outgoing interfaces for UDP sockets
|
||||
* lt:only allow pinged nodes into the DHT routing table
|
||||
* lt:introduce a recursive mutex to protect the alert_manager, and hold the
|
||||
mutex while calling user callbacks and plugin hooks
|
||||
* lt:revise alert priorities / torrent::on_resume_data_checked issue
|
||||
* lt:save the number of idle threads locally in
|
||||
disk_io_thread_pool::thread_active
|
||||
* lt:fix use after free in flush_range and flush_iovec
|
||||
* lt:honor torrent abort even on file check error
|
||||
* lt:use settings_pack::urlseed_wait_retry for default retry with http seeds
|
||||
* lt:fix storage initialization
|
||||
* lt:strtoll() returns LLONG_MAX if the input overflows, handle this case
|
||||
properly in the http parser
|
||||
* lt:remove the global cache of the current time, just use clock::now()
|
||||
* lt:deprecated alert::progress_notification alert category, split into
|
||||
finer grained categories
|
||||
* lt:disk_io_thread abort_hash_jobs duplicate code refactor
|
||||
* lt:fix part-file header allocation
|
||||
* lt:fix potential fd leak in enum_net_interfaces
|
||||
* lt:don't perform DNS lookups for the DHT bootstrap unless DHT is enabled
|
||||
* lt:avoid calls to .address() when looking for endpoint protocol
|
||||
* lt:removed unnecessary loop in request_a_block
|
||||
* lt:fix changing file priorities while checking interrupts checking
|
||||
* lt:fix issue where the current tracker would be skipped for the next
|
||||
tracker in the same tier
|
||||
* lt:remove redundant check in tracker announce
|
||||
* lt:minor fixes in utp_socket_impl
|
||||
* lt:stat files in the disk thread, in default_storage::initialize()
|
||||
instead of the constructor
|
||||
* lt:dynamically load getauxval so as to support older android devices
|
||||
* lt:track whether a file is eligible for using the partfile on a per-file
|
||||
basis
|
||||
* lt:define NETLINK_NO_ENOBUFS and IFA_D_DADFAILED if they don't exist
|
||||
* lt:fix reporting &redundant= in tracker announces
|
||||
* lt:fix windows build with UNC paths disabled
|
||||
* lt:fix issue querying block size from torrent before metadata has been
|
||||
received
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.17
|
||||
|
||||
* added EnumNet java API to help query the device network state/status/info
|
||||
* compiling with -mstackrealign for android x86, some devices have stack
|
||||
alignment issues
|
||||
* lt:fix tie-break in duplicate peer connection disconnect logic
|
||||
* lt:fix issue with SSL tracker connections left in CLOSE_WAIT state
|
||||
* lt:defer truncating existing files until the first time we write to them
|
||||
* lt:fix issue when receiving a torrent with 0-sized padfiles as magnet link
|
||||
* lt:fix issue resuming 1.0.x downloads with a file priority 0
|
||||
* lt:fix torrent_status::next_announce
|
||||
* lt:turn piece picker option flags into a proper type
|
||||
* lt:fix pad-file scalability issue
|
||||
* lt:made coalesce_reads/coalesce_writes settings take effect on linux
|
||||
and windows
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.16
|
||||
|
||||
* using version in native library names
|
||||
* updated to boost 1.66.0
|
||||
* lt:use unique peer_ids per connection
|
||||
* lt:fix tracker connection bind issue for IPv6 trackers
|
||||
* lt:fix error handling of merkle torrents
|
||||
* lt:fix error handling of unsupported hard-links
|
||||
* lt:raise auto piece size selection limit to 16 MB in create_torrent()
|
||||
* lt:mark up performance counter operations as noexcept
|
||||
* lt:fix noexcept marking on entry, and make move assignment
|
||||
* lt:using make_address instead of deprecated from_string when boost>=1.66
|
||||
* lt:block_size is a constant, no need in passing it around as a variable
|
||||
* lt:remove_peer() and attach_peer() error handling
|
||||
* lt:support forced shutdown/destruction of torrent objects
|
||||
* lt:improve error handling during session shutdown
|
||||
* lt:handle serious errors in on_accept_connection handler
|
||||
* lt:deprecate save_encryption_settings
|
||||
* lt:handle errors in peer_connection
|
||||
* lt:support asio handler allocators in deferred_handler
|
||||
* lt:don't heap-allocate handlers for incoming UDP packets
|
||||
* lt:make the chunk header parser properly fail at end of buffer, and not
|
||||
require zero terminated strings
|
||||
* lt:add getters for peer_class_filter and peer_class_type_filter
|
||||
* lt:attempt to fix disconnections when torrents enter upload mode due to
|
||||
failures
|
||||
* lt:fix local network address mappings
|
||||
* lt:deprecate status_code from tracker_error_alert
|
||||
* lt:make torrent_handler::set_priority() to use peer_classes
|
||||
* lt:improve type safety of plugin interface
|
||||
* lt:introduce a proper type for pex flags to improve type-safety
|
||||
* lt:fix reopening of listen sockets when disabling force-proxy
|
||||
* lt:fix build against boost-1.66, specifically the boost.asio changes
|
||||
* lt:fix asio debugging
|
||||
* lt:fix rate limit utp feature
|
||||
* lt:fix i2p support
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.15
|
||||
|
||||
* added java API to download magnet uri
|
||||
* fixed issue with prioritize_files swig wrapper
|
||||
* API change in TorrentHandle#saveResumeData, now it pass empty flags
|
||||
by default
|
||||
* using android NDK r16b
|
||||
* updated to OpenSSL 1.1.0g
|
||||
* lt:fix loading resume data when in seed mode
|
||||
* lt:fix incorrect use of make_tick_handler
|
||||
* lt:fix issue with initializing settings on session construction
|
||||
* lt:implemented support for magnet URI extension, select specific file
|
||||
indices for download, BEP53
|
||||
* lt:fix issue with receiving interested before metadata
|
||||
* lt:generate random keys for use in tracker announces, unique per torrent
|
||||
and the listen interface
|
||||
* lt:fix IPv6 tracker announce issue
|
||||
* lt:don't early move shared_ptr plugin in torrent::add_extension_fun
|
||||
* lt:fix force-proxy regression (udp sockets would not be opened)
|
||||
* lt:restore path sanitization behavior of ':'
|
||||
* lt:make tracker announces happen even if there are no open listen sockets
|
||||
* lt:fix issue where new listen sockets would not be opened when leaving
|
||||
force_proxy mode
|
||||
* lt:make sure the cork destructor doesn't leak exceptions
|
||||
* lt:stop posting alerts when the session is shutting down, solves some
|
||||
issues around destruction order
|
||||
* lt:add API to query whether alerts have been dropped or not
|
||||
* lt:keep updating aux::time_now() while there are announces, fixes an
|
||||
infinite loop during shutdown
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.14
|
||||
|
||||
* fixed invalid handling of download_priority_t and swig wrapper
|
||||
|
||||
1.2.0.13
|
||||
|
||||
* enable full SSL support in libtorrent
|
||||
* compiling with -fvisibility=hidden and -Os
|
||||
* using android NDK r16 beta2
|
||||
* updated to boost 1.65.1
|
||||
* internal session in SessionManager blocks privileged ports and
|
||||
only allows 80 and 443 for possible web seeds connections
|
||||
* lt:enable/disable the internal ip notifier with new setting
|
||||
* lt:fix issue of null m_part_file in default_storage readv/writev
|
||||
* lt:added reopen_network_sockets method to allow manual reopen of
|
||||
listen/outgoing sockets
|
||||
* lt:using NETLINK_NO_ENOBUFS to ignore ENOBUFS in netlink based ip notifier
|
||||
* lt:add limit of max 50 upnp mappings and recover free global mappings
|
||||
* lt:creating part file if needed only in set_file_priorities
|
||||
* lt:fix full allocation failure on APFS
|
||||
* lt:make parse_magnet_uri return the add_torrent_params instead of
|
||||
taking an in-out parameter
|
||||
* lt:pick standard std::aligned_union if using clang
|
||||
* lt:make disk_buffer_holder know the size of the buffer it holds, to fix
|
||||
buffer overrun in chained_buffer
|
||||
* lt:fix infinite loop when parsing certain invalid magnet links
|
||||
* lt:don't delete pieces from cache with refcount > 0
|
||||
* lt:don't try to connect to a global address with a local source address
|
||||
* lt:fix parsing of torrents with certain invalid filenames
|
||||
* lt:fix leak of peer_class objects (when setting per-torrent rate limits)
|
||||
* lt:more strict filename sanitation
|
||||
* lt:improve handling of case where a torrent file has no files in it
|
||||
* lt:clean up and fix edge cases in update_path_index
|
||||
* lt:fix issue with the name in single file torrents being sanitized away
|
||||
* lt:make the chunk header parser a bit more strict and accurate
|
||||
* lt:fix integer overflow in torrent_info
|
||||
* lt:fix windows file preallocation issue
|
||||
* lt:fix integer overflow in whole_pieces_threshold logic
|
||||
* lt:DHT nodes should only handle requests on their socket
|
||||
* lt:bump priority of storage_moved_alert and storage_moved_failed_alert
|
||||
* lt:deprecate lock_files settings
|
||||
* lt:fix uTP path MTU discovery issue on windows, DF bit was not set
|
||||
correctly
|
||||
* lt:select which DHT port to report based on the connection's local
|
||||
endpoint
|
||||
* lt:avoid port mapping of local IPv6 addresses
|
||||
* lt:include endpoint in tracker alerts
|
||||
* lt:hold an owning reference to storage objects in try_flush_write_blocks
|
||||
* lt:read_piece: handle failure to allocate piece buffer
|
||||
* lt:treat unique local IPv6 addresses as local
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.12
|
||||
|
||||
* updated to boost 1.65
|
||||
* storing external address and listen endpoints as strings for better
|
||||
performance
|
||||
* improved creation of peers part of magnet links
|
||||
* lt:fixed netlink based network interface enumeration
|
||||
* lt:remove support for using a pool allocator for disk buffers
|
||||
* lt:fix IPv6 tracker support by performing the second announce in
|
||||
more cases
|
||||
* lt:fix issue in UTF-8 encoding validation
|
||||
* lt:fix infinite loop when parsing torrents whose filenames have zeroes
|
||||
* lt:fix invalid read in parse_int() in bdecode_node()
|
||||
* lt:don't create web seed connections if the torrent is upload only
|
||||
* lt:fix issue with very long tracker and web seed URLs
|
||||
* lt:fix issue where paths were not correctly coalesced when adding files
|
||||
to file_storage (used more memory than necessary)
|
||||
* lt:fix issue of force-recheck or seeding from read-only media, torrents
|
||||
with empty files in them
|
||||
* lt:fix force-recheck issue (new files would not be picked up)
|
||||
* lt:source code cleanup, performance and stability
|
||||
|
||||
1.2.0.11
|
||||
|
||||
* using android NDK r15c
|
||||
* using -O2 for all architectures
|
||||
* lt:fix to clearing of piece picker in suggest_read_cache mode
|
||||
* lt:fix memory issues with listen sockets references
|
||||
* lt:fix bug where the resume data would fail to load the piece bitmask for
|
||||
seeds when suggest_cache was enabled
|
||||
* lt:submit disk jobs in read_piece()
|
||||
* lt:add reserve entry::to_string() (optimization)
|
||||
* lt:refactor several flags to torrent_handle::get_flags/set_flags
|
||||
* lt:added block_uploaded_alert to allow client to track upload activity
|
||||
* lt:fix inconsistency in file_priorities and override_resume_data behavior
|
||||
* lt:remove call _strchr (optimization)
|
||||
* lt:fix bandwidth rate limit calculation
|
||||
* lt:fix handling of SSL listen sockets
|
||||
* lt:don't move listen_socket_t when deleting sockets
|
||||
* lt:avoid executing timed async task if the dht node is already removed
|
||||
* lt:reject DHT put messages with incorrect bencoding
|
||||
* lt:fix backwards compatibility issue when loading the torrent info dict
|
||||
from resume data
|
||||
* lt:fix regression where paused torrents could not have their queue
|
||||
position changed
|
||||
* lt:use netlink to enumerate network interfaces on linux
|
||||
* lt:fix out-of-bounds read in bdecode
|
||||
* lt:fix re-check issue after move_storage
|
||||
* lt:avoid runtime fail with wrong arguments in upnp::update_map
|
||||
* lt:handle invalid arguments to set_piece_deadline()
|
||||
* lt:fix that move_storage did not work for torrents without metadata
|
||||
* lt:fix check for fully allocated file on windows
|
||||
* lt:defer reconnecting peers to after the second_tick loop
|
||||
* lt:implemented support for BEP 51
|
||||
* lt:source code cleanup and stability
|
||||
|
||||
1.2.0.10
|
||||
|
||||
* added swig interface to announce_endpoint
|
||||
|
||||
1.2.0.9
|
||||
|
||||
* updated to OpenSSL 1.1.0f
|
||||
* updated to boost 1.64
|
||||
* using android NDK r15
|
||||
* avoid automatic UTF-8 conversion in JNI side when using string_view
|
||||
* lt:fix bandwidth rate limit calculation
|
||||
* lt:fix for what appears to be an clang/llvm miscompilation
|
||||
* lt:only listen on preferred IPv6 addresses on Windows
|
||||
* lt:when stopping a torrent, never perform a name lookup on the tracker,
|
||||
only announce to trackers whose IP we already know. This is expected to
|
||||
make shutdowns not hang
|
||||
* lt:fix previously faulty fix to enum_routes
|
||||
* lt:delay 5 seconds before reconnecting socks5 proxy for UDP ASSOCIATE
|
||||
* lt:fix NAT-PMP crash when removing a mapping at the wrong time
|
||||
* lt:fix race condition in storage tick handling in disk_io_thread
|
||||
* lt:keep iterating over endpoints if one is found to be done in
|
||||
tracker announce loop
|
||||
* lt:don't abort the existing torrent when attempting to add it again
|
||||
* lt:fix IPv6 UTP assertion failure, close listen sockets after closing
|
||||
all connections
|
||||
* lt:fix branch factor overflow in DHT traversal algorithm
|
||||
* lt:bind upnp requests to correct local address
|
||||
* lt:don't combine reuseaddr and exclusive addruse on windows
|
||||
* lt:save resume data when removing web seeds
|
||||
* lt:fix proxying of https connections
|
||||
* lt:fix race condition in disk I/O storage class
|
||||
* lt:avoid extra sha1_hash memory copy in create_torrent
|
||||
* lt:fix http connection timeout on multi-homed hosts
|
||||
* lt:implemented multi-home support
|
||||
* lt:make DHT bootstrapping more robust by not throwing away nodes
|
||||
* lt:need_save_resume_data() will no longer return true every 15 minutes
|
||||
* lt:create a separate DHT node for each listen socket
|
||||
* lt:avoid connections to trackers when the event is stopped and
|
||||
stop_tracker_timeout <= 0
|
||||
* lt:fix storage destruction order issue
|
||||
* lt:fix memory leak in the disk cache
|
||||
* lt:magnet links: unescape hash parameter
|
||||
* lt:fix double free in disk cache
|
||||
* lt:fix typo in natpmp::start
|
||||
* lt:remove mutex-release hack in file_pool
|
||||
* lt:source code cleanup and stability
|
||||
|
||||
1.2.0.8
|
||||
|
||||
* fixed synchronization issues in SessionManager#stop
|
||||
|
||||
1.2.0.7
|
||||
|
||||
* added support for ip_notifier in macOS
|
||||
* added resolver_cache_timeout setting for internal host name resolver
|
||||
* improved public API
|
||||
* internal fixes in libtorrent
|
||||
|
||||
1.2.0.7-RC3
|
||||
|
||||
* start of changelog
|
@ -0,0 +1,542 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.add_torrent_params;
|
||||
import com.frostwire.jlibtorrent.swig.error_code;
|
||||
import com.frostwire.jlibtorrent.swig.int_vector;
|
||||
import com.frostwire.jlibtorrent.swig.storage_mode_t;
|
||||
import com.frostwire.jlibtorrent.swig.string_int_pair;
|
||||
import com.frostwire.jlibtorrent.swig.string_int_pair_vector;
|
||||
import com.frostwire.jlibtorrent.swig.string_vector;
|
||||
import com.frostwire.jlibtorrent.swig.tcp_endpoint_vector;
|
||||
import com.frostwire.jlibtorrent.swig.torrent_flags_t;
|
||||
import com.frostwire.jlibtorrent.swig.torrent_info;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link AddTorrentParams} is a parameter pack for adding torrents to a
|
||||
* session. The key fields when adding a torrent are:
|
||||
* <ul>
|
||||
* <li>ti - when you have a .torrent file</li>
|
||||
* <li>url - when you have a magnet link or http URL to the .torrent file</li>
|
||||
* <li>info_hash - when all you have is an info-hash (this is similar to a magnet link)</li>
|
||||
* </ul>
|
||||
* One of those fields need to be set. Another mandatory field is
|
||||
* {@link #savePath()}. The {@link AddTorrentParams} object is passed into one of the
|
||||
* {@link SessionHandle#addTorrent(AddTorrentParams, ErrorCode)} overloads or
|
||||
* {@link SessionHandle#asyncAddTorrent(AddTorrentParams)}.
|
||||
* <p>
|
||||
* If you only specify the info-hash, the torrent file will be downloaded
|
||||
* from peers, which requires them to support the metadata extension. It also
|
||||
* takes an optional {@link #name()} argument. This may be left empty in case no
|
||||
* name should be assigned to the torrent. In case it's not, the name is
|
||||
* used for the torrent as long as it doesn't have metadata.
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class AddTorrentParams {
|
||||
|
||||
private final add_torrent_params p;
|
||||
|
||||
/**
|
||||
* The native object
|
||||
*
|
||||
* @param p the native object
|
||||
*/
|
||||
public AddTorrentParams(add_torrent_params p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty parameters object with the default storage.
|
||||
*/
|
||||
public AddTorrentParams() {
|
||||
this(add_torrent_params.create_instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the native object
|
||||
*/
|
||||
public add_torrent_params swig() {
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filled in by the constructor. It is used for forward binary compatibility.
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
public int version() {
|
||||
return p.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TorrentInfo} object with the torrent to add.
|
||||
*
|
||||
* @return the torrent info or null if not set
|
||||
*/
|
||||
public TorrentInfo torrentInfo() {
|
||||
torrent_info ti = p.ti_ptr();
|
||||
return ti != null && ti.is_valid() ? new TorrentInfo(ti) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TorrentInfo} object with the torrent to add.
|
||||
*
|
||||
* @param ti the torrent info
|
||||
*/
|
||||
public void torrentInfo(TorrentInfo ti) {
|
||||
p.set_ti(ti.swig());
|
||||
}
|
||||
|
||||
/**
|
||||
* If the torrent doesn't have a tracker, but relies on the DHT to find
|
||||
* peers, the {@link #trackers(List)} can specify tracker URLs for the
|
||||
* torrent.
|
||||
*
|
||||
* @return the list of trackers
|
||||
*/
|
||||
public ArrayList<String> trackers() {
|
||||
string_vector v = p.get_trackers();
|
||||
int size = (int) v.size();
|
||||
ArrayList<String> l = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(v.get(i));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the torrent doesn't have a tracker, but relies on the DHT to find
|
||||
* peers, this method can specify tracker URLs for the torrent.
|
||||
*
|
||||
* @param value the list of trackers
|
||||
*/
|
||||
public void trackers(List<String> value) {
|
||||
string_vector v = new string_vector();
|
||||
|
||||
for (String s : value) {
|
||||
v.push_back(s);
|
||||
}
|
||||
|
||||
p.set_trackers(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* The tiers the URLs in {@link #trackers()} belong to. Trackers belonging to
|
||||
* different tiers may be treated differently, as defined by the multi
|
||||
* tracker extension. This is optional, if not specified trackers are
|
||||
* assumed to be part of tier 0, or whichever the last tier was as
|
||||
* iterating over the trackers.
|
||||
*
|
||||
* @return the list of trackers tiers
|
||||
*/
|
||||
public ArrayList<Integer> trackerTiers() {
|
||||
int_vector v = p.get_tracker_tiers();
|
||||
int size = (int) v.size();
|
||||
ArrayList<Integer> l = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(v.get(i));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tiers the URLs in {@link #trackers()} belong to. Trackers belonging to
|
||||
* different tiers may be treated differently, as defined by the multi
|
||||
* tracker extension. This is optional, if not specified trackers are
|
||||
* assumed to be part of tier 0, or whichever the last tier was as
|
||||
* iterating over the trackers.
|
||||
*
|
||||
* @param value the list of trackers tiers
|
||||
*/
|
||||
public void trackerTiers(List<Integer> value) {
|
||||
int_vector v = new int_vector();
|
||||
|
||||
for (Integer t : value) {
|
||||
v.push_back(t);
|
||||
}
|
||||
|
||||
p.set_tracker_tiers(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of hostname and port pairs, representing DHT nodes to be added
|
||||
* to the session (if DHT is enabled). The hostname may be an IP address.
|
||||
*
|
||||
* @return the list of DHT nodes
|
||||
*/
|
||||
public ArrayList<Pair<String, Integer>> dhtNodes() {
|
||||
string_int_pair_vector v = p.get_dht_nodes();
|
||||
int size = (int) v.size();
|
||||
ArrayList<Pair<String, Integer>> l = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
string_int_pair n = v.get(i);
|
||||
l.add(new Pair<>(n.getFirst(), n.getSecond()));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of hostname and port pairs, representing DHT nodes to be added
|
||||
* to the session (if DHT is enabled). The hostname may be an IP address.
|
||||
*
|
||||
* @param value the list of DHT nodes
|
||||
*/
|
||||
public void dhtNodes(List<Pair<String, Integer>> value) {
|
||||
string_int_pair_vector v = new string_int_pair_vector();
|
||||
|
||||
for (Pair<String, Integer> p : value) {
|
||||
v.push_back(p.to_string_int_pair());
|
||||
}
|
||||
|
||||
p.set_dht_nodes(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String name() {
|
||||
return p.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value the name
|
||||
*/
|
||||
public void name(String value) {
|
||||
p.setName(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The path where the torrent is or will be stored. Note that this may
|
||||
* also be stored in resume data. If you want the save path saved in
|
||||
* the resume data to be used, you need to set the
|
||||
* flag_use_resume_save_path flag.
|
||||
* <p>
|
||||
* .. note::
|
||||
* On windows this path (and other paths) are interpreted as UNC
|
||||
* paths. This means they must use backslashes as directory separators
|
||||
*
|
||||
* @return the save path
|
||||
*/
|
||||
public String savePath() {
|
||||
return p.getSave_path();
|
||||
}
|
||||
|
||||
/**
|
||||
* The path where the torrent is or will be stored. Note that this may
|
||||
* also be stored in resume data. If you want the save path saved in
|
||||
* the resume data to be used, you need to set the
|
||||
* flag_use_resume_save_path flag.
|
||||
* <p>
|
||||
* .. note::
|
||||
* On windows this path (and other paths) are interpreted as UNC
|
||||
* paths. This means they must use backslashes as directory separators
|
||||
*
|
||||
* @param value the save path
|
||||
*/
|
||||
public void savePath(String value) {
|
||||
p.setSave_path(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the storage mode
|
||||
* @see StorageMode
|
||||
*/
|
||||
public StorageMode storageMode() {
|
||||
return StorageMode.fromSwig(p.getStorage_mode().swigValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value the storage mode
|
||||
* @see StorageMode
|
||||
*/
|
||||
public void storageMode(StorageMode value) {
|
||||
p.setStorage_mode(storage_mode_t.swigToEnum(value.swig()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The default tracker id to be used when announcing to trackers. By
|
||||
* default this is empty, and no tracker ID is used, since this is an
|
||||
* optional argument. If a tracker returns a tracker ID, that ID is used
|
||||
* instead of this.
|
||||
*
|
||||
* @return the trackerid url parameter
|
||||
*/
|
||||
public String trackerId() {
|
||||
return p.getTrackerid();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default tracker id to be used when announcing to trackers. By
|
||||
* default this is empty, and no tracker ID is used, since this is an
|
||||
* optional argument. If a tracker returns a tracker ID, that ID is used
|
||||
* instead of this.
|
||||
*
|
||||
* @param value the trackerid url parameter
|
||||
*/
|
||||
public void trackerId(String value) {
|
||||
p.setTrackerid(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to the info hash of the torrent to add in case the info-hash
|
||||
* is the only known property of the torrent. i.e. you don't have a
|
||||
* .torrent file nor a magnet link.
|
||||
*
|
||||
* @return the info-hash
|
||||
*/
|
||||
public Sha1Hash infoHash() {
|
||||
return new Sha1Hash(p.getInfo_hash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to the info hash of the torrent to add in case the info-hash
|
||||
* is the only known property of the torrent. i.e. you don't have a
|
||||
* .torrent file nor a magnet link.
|
||||
*
|
||||
* @param value the info-hash
|
||||
*/
|
||||
public void infoHash(Sha1Hash value) {
|
||||
p.setInfo_hash(value.swig());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return max uploads limit
|
||||
*/
|
||||
public int maxUploads() {
|
||||
return p.getMax_uploads();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value max uploads limit
|
||||
*/
|
||||
public void maxUploads(int value) {
|
||||
p.setMax_uploads(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return max connections limit
|
||||
*/
|
||||
public int maxConnections() {
|
||||
return p.getMax_connections();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value max connections limit
|
||||
*/
|
||||
public void maxConnections(int value) {
|
||||
p.setMax_connections(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return upload limit
|
||||
*/
|
||||
public int uploadLimit() {
|
||||
return p.getUpload_limit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value upload limit
|
||||
*/
|
||||
public void uploadLimit(int value) {
|
||||
p.setUpload_limit(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return download limit
|
||||
*/
|
||||
public int downloadLimit() {
|
||||
return p.getDownload_limit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value download limit
|
||||
*/
|
||||
public void downloadLimit(int value) {
|
||||
p.setDownload_limit(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags controlling aspects of this torrent and how it's added.
|
||||
*
|
||||
* @return the flags
|
||||
*/
|
||||
public torrent_flags_t flags() {
|
||||
return p.getFlags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags controlling aspects of this torrent and how it's added.
|
||||
*
|
||||
* @param flags the flags
|
||||
*/
|
||||
public void flags(torrent_flags_t flags) {
|
||||
p.setFlags(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Url seeds to be added to the torrent (`BEP 17`_).
|
||||
*
|
||||
* @return the url seeds
|
||||
*/
|
||||
public ArrayList<String> urlSeeds() {
|
||||
string_vector v = p.get_url_seeds();
|
||||
int size = (int) v.size();
|
||||
ArrayList<String> l = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(v.get(i));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Url seeds to be added to the torrent (`BEP 17`_).
|
||||
*
|
||||
* @param value the url seeds
|
||||
*/
|
||||
public void urlSeeds(List<String> value) {
|
||||
string_vector v = new string_vector();
|
||||
|
||||
for (String s : value) {
|
||||
v.push_back(s);
|
||||
}
|
||||
|
||||
p.set_url_seeds(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be set to control the initial file priorities when adding a
|
||||
* torrent. The semantics are the same as for
|
||||
* {@link TorrentHandle#prioritizeFiles(Priority[])}.
|
||||
*
|
||||
* @param priorities the priorities
|
||||
*/
|
||||
public void filePriorities(Priority[] priorities) {
|
||||
p.set_file_priorities2(Priority.array2byte_vector(priorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the priorities for each individual piece in the torrent. Each
|
||||
* element in the vector represent the piece with the same index. If you
|
||||
* set both file- and piece priorities, file priorities will take
|
||||
* precedence.
|
||||
*
|
||||
* @param priorities the priorities
|
||||
*/
|
||||
public void piecePriorities(Priority[] priorities) {
|
||||
p.set_piece_priorities2(Priority.array2byte_vector(priorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Peers to add to the torrent, to be tried to be connected to as
|
||||
* bittorrent peers.
|
||||
*
|
||||
* @return the peers list
|
||||
*/
|
||||
public ArrayList<TcpEndpoint> peers() {
|
||||
tcp_endpoint_vector v = p.get_peers();
|
||||
int size = (int) v.size();
|
||||
ArrayList<TcpEndpoint> l = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(new TcpEndpoint(v.get(i)));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peers to add to the torrent, to be tried to be connected to as
|
||||
* bittorrent peers.
|
||||
*
|
||||
* @param value the peers list
|
||||
*/
|
||||
public void peers(List<TcpEndpoint> value) {
|
||||
tcp_endpoint_vector v = new tcp_endpoint_vector();
|
||||
|
||||
for (TcpEndpoint endp : value) {
|
||||
v.push_back(endp.swig());
|
||||
}
|
||||
|
||||
p.set_peers(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Peers banned from this torrent. The will not be connected to.
|
||||
*
|
||||
* @return the peers list
|
||||
*/
|
||||
public ArrayList<TcpEndpoint> bannedPeers() {
|
||||
tcp_endpoint_vector v = p.get_banned_peers();
|
||||
int size = (int) v.size();
|
||||
ArrayList<TcpEndpoint> l = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(new TcpEndpoint(v.get(i)));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peers banned from this torrent. The will not be connected to.
|
||||
*
|
||||
* @param value the peers list
|
||||
*/
|
||||
public void bannedPeers(List<TcpEndpoint> value) {
|
||||
tcp_endpoint_vector v = new tcp_endpoint_vector();
|
||||
|
||||
for (TcpEndpoint endp : value) {
|
||||
v.push_back(endp.swig());
|
||||
}
|
||||
|
||||
p.set_banned_peers(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an instance with the default storage
|
||||
*/
|
||||
public static AddTorrentParams createInstance() {
|
||||
return new AddTorrentParams(add_torrent_params.create_instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an instance with a disabled storage
|
||||
*/
|
||||
public static AddTorrentParams createInstanceDisabledStorage() {
|
||||
return new AddTorrentParams(add_torrent_params.create_instance_disabled_storage());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an instance with a zero storage
|
||||
*/
|
||||
public static AddTorrentParams createInstanceZeroStorage() {
|
||||
return new AddTorrentParams(add_torrent_params.create_instance_zero_storage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to parse a magnet uri and fill the parameters.
|
||||
*
|
||||
* @param uri the magnet uri
|
||||
* @return the params object filled with the data from the magnet
|
||||
*/
|
||||
public static AddTorrentParams parseMagnetUri(String uri) {
|
||||
error_code ec = new error_code();
|
||||
add_torrent_params params = add_torrent_params.parse_magnet_uri(uri, ec);
|
||||
if (ec.value() != 0) {
|
||||
throw new IllegalArgumentException("Invalid magnet uri: " + ec.message());
|
||||
}
|
||||
return new AddTorrentParams(params);
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.address;
|
||||
import com.frostwire.jlibtorrent.swig.error_code;
|
||||
|
||||
/**
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class Address implements Comparable<Address>, Cloneable {
|
||||
|
||||
private final address addr;
|
||||
|
||||
/**
|
||||
* @param addr the native object
|
||||
*/
|
||||
public Address(address addr) {
|
||||
this.addr = addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address from an IPv4 address string in dotted decimal form,
|
||||
* or from an IPv6 address in hexadecimal notation.
|
||||
*
|
||||
* @param ip the ip string representation
|
||||
*/
|
||||
public Address(String ip) {
|
||||
error_code ec = new error_code();
|
||||
this.addr = address.from_string(ip, ec);
|
||||
if (ec.value() != 0) {
|
||||
throw new IllegalArgumentException(ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public Address() {
|
||||
this(new address());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return native object
|
||||
*/
|
||||
public address swig() {
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the address is an IP version 4 address.
|
||||
*
|
||||
* @return if it's an IPv4 address
|
||||
*/
|
||||
public boolean isV4() {
|
||||
return addr.is_v4();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the address is an IP version 6 address.
|
||||
*
|
||||
* @return if it's an IPv6 address
|
||||
*/
|
||||
public boolean isV6() {
|
||||
return addr.is_v6();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the address is a loopback address.
|
||||
*
|
||||
* @return if it's a loopback address
|
||||
*/
|
||||
public boolean isLoopback() {
|
||||
return addr.is_loopback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the address is unspecified.
|
||||
*
|
||||
* @return if it's an unspecified address
|
||||
*/
|
||||
public boolean isUnspecified() {
|
||||
return addr.is_unspecified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the address is a multicast address.
|
||||
*
|
||||
* @return if it's an multicast address
|
||||
*/
|
||||
public boolean isMulticast() {
|
||||
return addr.is_multicast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare addresses for ordering.
|
||||
*
|
||||
* @param o the other address
|
||||
* @return -1, 0 or 1
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Address o) {
|
||||
return address.compare(this.addr, o.addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address as a string in dotted decimal format.
|
||||
*
|
||||
* @return string representation
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address clone() {
|
||||
return new Address(new address(addr));
|
||||
}
|
||||
|
||||
static String toString(address a) {
|
||||
error_code ec = new error_code();
|
||||
String s = a.to_string(ec);
|
||||
if (ec.value() != 0) {
|
||||
s = "<invalid address>";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.alerts.Alert;
|
||||
|
||||
/**
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public interface AlertListener {
|
||||
|
||||
/**
|
||||
* List of alert types filtered by this listener.
|
||||
* Return `null` if you intend to listen to all alerts.
|
||||
*
|
||||
* @return the types filter
|
||||
*/
|
||||
int[] types();
|
||||
|
||||
/**
|
||||
* @param alert the alert
|
||||
*/
|
||||
void alert(Alert<?> alert);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.alerts.Alert;
|
||||
|
||||
/**
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
final class AlertMulticaster implements AlertListener {
|
||||
|
||||
private final AlertListener a;
|
||||
private final AlertListener b;
|
||||
|
||||
public AlertMulticaster(AlertListener a, AlertListener b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] types() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alert(Alert<?> alert) {
|
||||
a.alert(alert);
|
||||
b.alert(alert);
|
||||
}
|
||||
|
||||
public static AlertListener add(AlertListener a, AlertListener b) {
|
||||
return addInternal(a, b);
|
||||
}
|
||||
|
||||
public static AlertListener remove(AlertListener l, AlertListener oldl) {
|
||||
return removeInternal(l, oldl);
|
||||
}
|
||||
|
||||
private AlertListener remove(AlertListener oldl) {
|
||||
if (oldl == a) return b;
|
||||
if (oldl == b) return a;
|
||||
AlertListener a2 = removeInternal(a, oldl);
|
||||
AlertListener b2 = removeInternal(b, oldl);
|
||||
if (a2 == a && b2 == b) {
|
||||
return this; // it's not here
|
||||
}
|
||||
return addInternal(a2, b2);
|
||||
}
|
||||
|
||||
private static AlertListener addInternal(AlertListener a, AlertListener b) {
|
||||
if (a == null) return b;
|
||||
if (b == null) return a;
|
||||
return new AlertMulticaster(a, b);
|
||||
}
|
||||
|
||||
private static AlertListener removeInternal(AlertListener l, AlertListener oldl) {
|
||||
if (l == oldl || l == null) {
|
||||
return null;
|
||||
} else if (l instanceof AlertMulticaster) {
|
||||
return ((AlertMulticaster) l).remove(oldl);
|
||||
} else {
|
||||
return l; // it's not here
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.announce_endpoint;
|
||||
|
||||
/**
|
||||
* Announces are sent to each tracker using every listen socket, this class
|
||||
* holds information about one listen socket for one tracker.
|
||||
* <p>
|
||||
* This class is a lightweight version of the native {@link announce_endpoint},
|
||||
* and only carries a subset of all the information. However, it's completely
|
||||
* open for custom use or optimization to accommodate client necessities.
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public class AnnounceEndpoint {
|
||||
|
||||
protected String message;
|
||||
protected ErrorCode lastError;
|
||||
protected String localEndpoint;
|
||||
protected long nextAnnounce;
|
||||
protected long minAnnounce;
|
||||
protected int scrapeIncomplete;
|
||||
protected int scrapeComplete;
|
||||
protected int scrapeDownloaded;
|
||||
protected int fails;
|
||||
protected boolean updating;
|
||||
protected boolean isWorking;
|
||||
|
||||
/**
|
||||
* @param e the native object
|
||||
*/
|
||||
public AnnounceEndpoint(announce_endpoint e) {
|
||||
init(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this tracker has returned an error or warning message
|
||||
* that message is stored here.
|
||||
*
|
||||
* @return the error or warning message
|
||||
*/
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this tracker failed the last time it was contacted
|
||||
* this error code specifies what error occurred.
|
||||
*
|
||||
* @return the last error code
|
||||
*/
|
||||
public ErrorCode lastError() {
|
||||
return lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* The local endpoint of the listen interface associated with this endpoint.
|
||||
*
|
||||
* @return the local endpoint
|
||||
*/
|
||||
public String localEndpoint() {
|
||||
return localEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* The time of next tracker announce in milliseconds.
|
||||
*
|
||||
* @return the time of next tracker announce in milliseconds
|
||||
*/
|
||||
public long nextAnnounce() {
|
||||
return nextAnnounce;
|
||||
}
|
||||
|
||||
/**
|
||||
* No announces before this time.
|
||||
*/
|
||||
public long minAnnounce() {
|
||||
return minAnnounce;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is either -1 or the scrape information this tracker last
|
||||
* responded with. Incomplete is the current number of downloaders in
|
||||
* the swarm.
|
||||
* <p>
|
||||
* If this tracker has returned scrape data, these fields are filled in
|
||||
* with valid numbers. Otherwise they are set to -1.
|
||||
*
|
||||
* @return the number of current downloaders
|
||||
*/
|
||||
public int scrapeIncomplete() {
|
||||
return scrapeIncomplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is either -1 or the scrape information this tracker last
|
||||
* responded with. Complete is the current number of seeds in the swarm.
|
||||
* <p>
|
||||
* If this tracker has returned scrape data, these fields are filled in
|
||||
* with valid numbers. Otherwise they are set to -1.
|
||||
*
|
||||
* @return the current number of seeds
|
||||
*/
|
||||
public int scrapeComplete() {
|
||||
return scrapeComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is either -1 or the scrape information this tracker last
|
||||
* responded with. Downloaded is the cumulative number of completed
|
||||
* downloads of this torrent, since the beginning of time
|
||||
* (from this tracker's point of view).
|
||||
* <p>
|
||||
* If this tracker has returned scrape data, these fields are filled in
|
||||
* with valid numbers. Otherwise they are set to -1.
|
||||
*
|
||||
* @return the cumulative number of completed downloads
|
||||
*/
|
||||
public int scrapeDownloaded() {
|
||||
return scrapeDownloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of times in a row we have failed to announce to this tracker.
|
||||
*
|
||||
* @return the number of fails
|
||||
*/
|
||||
public int fails() {
|
||||
return fails;
|
||||
}
|
||||
|
||||
/**
|
||||
* True while we're waiting for a response from the tracker.
|
||||
*/
|
||||
public boolean updating() {
|
||||
return updating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the last time we tried to announce to this
|
||||
* tracker succeeded, or if we haven't tried yet.
|
||||
*/
|
||||
public boolean isWorking() {
|
||||
return isWorking;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: use this with care and only if necessary.
|
||||
*
|
||||
* @param e the native object
|
||||
*/
|
||||
protected void init(announce_endpoint e) {
|
||||
message = Vectors.byte_vector2ascii(e.get_message());
|
||||
lastError = new ErrorCode(e.getLast_error());
|
||||
localEndpoint = new TcpEndpoint(e.getLocal_endpoint()).toString();
|
||||
nextAnnounce = e.get_next_announce();
|
||||
minAnnounce = e.get_min_announce();
|
||||
scrapeIncomplete = e.getScrape_incomplete();
|
||||
scrapeComplete = e.getScrape_complete();
|
||||
scrapeDownloaded = e.getScrape_downloaded();
|
||||
fails = e.getFails();
|
||||
updating = e.getUpdating();
|
||||
isWorking = e.is_working();
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.announce_endpoint_vector;
|
||||
import com.frostwire.jlibtorrent.swig.announce_entry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class holds information about one bittorrent tracker, as it
|
||||
* relates to a specific torrent.
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class AnnounceEntry {
|
||||
|
||||
private final announce_entry e;
|
||||
|
||||
/**
|
||||
* @param e the native object
|
||||
*/
|
||||
public AnnounceEntry(announce_entry e) {
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tracker announce entry with {@code u} as the URL.
|
||||
*
|
||||
* @param url the tracker url
|
||||
*/
|
||||
public AnnounceEntry(String url) {
|
||||
this(new announce_entry(Vectors.ascii2byte_vector(url)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the native object
|
||||
*/
|
||||
public announce_entry swig() {
|
||||
return e;
|
||||
}
|
||||
|
||||
public List<AnnounceEndpoint> endpoints() {
|
||||
announce_endpoint_vector v = e.getEndpoints();
|
||||
int size = (int) v.size();
|
||||
ArrayList<AnnounceEndpoint> l = new ArrayList<>(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(new AnnounceEndpoint(v.get(i)));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracker URL as it appeared in the torrent file.
|
||||
*
|
||||
* @return the tracker url
|
||||
*/
|
||||
public String url() {
|
||||
return Vectors.byte_vector2ascii(e.get_url());
|
||||
}
|
||||
|
||||
public void url(String value) {
|
||||
e.set_url(Vectors.ascii2byte_vector(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* The current {@code &trackerid=} argument passed to the tracker.
|
||||
* this is optional and is normally empty (in which case no
|
||||
* trackerid is sent).
|
||||
*
|
||||
* @return the trackerid url argument
|
||||
*/
|
||||
public String trackerId() {
|
||||
return Vectors.byte_vector2ascii(e.get_trackerid());
|
||||
}
|
||||
|
||||
public void trackerId(String value) {
|
||||
e.set_trackerid(Vectors.ascii2byte_vector(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* The tier this tracker belongs to.
|
||||
*
|
||||
* @return the tier number
|
||||
*/
|
||||
public int tier() {
|
||||
return e.getTier();
|
||||
}
|
||||
|
||||
public void tier(short value) {
|
||||
e.setTier(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The max number of failures to announce to this tracker in
|
||||
* a row, before this tracker is not used anymore. 0 means unlimited.
|
||||
*
|
||||
* @return the max number of failures allowed
|
||||
*/
|
||||
public int failLimit() {
|
||||
return e.getFail_limit();
|
||||
}
|
||||
|
||||
public void failLimit(short value) {
|
||||
e.setFail_limit(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* A bitmask specifying which sources we got this tracker from.
|
||||
*
|
||||
* @return the source bitmask
|
||||
*/
|
||||
public int source() {
|
||||
return e.getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true the first time we receive a valid response
|
||||
* from this tracker.
|
||||
*
|
||||
* @return if the tracker has received a valid response
|
||||
*/
|
||||
public boolean isVerified() {
|
||||
return e.getVerified();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.bdecode_node;
|
||||
import com.frostwire.jlibtorrent.swig.byte_vector;
|
||||
import com.frostwire.jlibtorrent.swig.error_code;
|
||||
|
||||
/**
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class BDecodeNode {
|
||||
|
||||
private final bdecode_node n;
|
||||
private final byte_vector buffer;
|
||||
|
||||
/**
|
||||
* @param n the native object
|
||||
*/
|
||||
public BDecodeNode(bdecode_node n) {
|
||||
this(n, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to keep the buffer reference around.
|
||||
*
|
||||
* @param n the native object
|
||||
* @param buffer the buffer backing up the native object
|
||||
*/
|
||||
public BDecodeNode(bdecode_node n, byte_vector buffer) {
|
||||
this.n = n;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the native object
|
||||
*/
|
||||
public bdecode_node swig() {
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods returns the internal buffer or null
|
||||
* if it was constructed without one.
|
||||
* <p>
|
||||
* This also prevent premature garbage collection in case
|
||||
* the node was created from a constructed buffer.
|
||||
*
|
||||
* @return the pinned buffer
|
||||
*/
|
||||
public byte_vector buffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSON style string representation
|
||||
*
|
||||
* @return the string representation
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return bdecode_node.to_string(n, false, 2);
|
||||
}
|
||||
|
||||
public static BDecodeNode bdecode(byte[] data) {
|
||||
byte_vector buffer = Vectors.bytes2byte_vector(data);
|
||||
bdecode_node n = new bdecode_node();
|
||||
error_code ec = new error_code();
|
||||
int ret = bdecode_node.bdecode(buffer, n, ec);
|
||||
|
||||
if (ret == 0) {
|
||||
return new BDecodeNode(n, buffer);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can't decode data: " + ec.message());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.block_info;
|
||||
|
||||
/**
|
||||
* Holds the state of a block in a piece. Who we requested
|
||||
* it from and how far along we are at downloading it.
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class BlockInfo {
|
||||
|
||||
private final block_info b;
|
||||
|
||||
/**
|
||||
* @param b the native object
|
||||
*/
|
||||
public BlockInfo(block_info b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the native object
|
||||
*/
|
||||
public block_info swig() {
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* The peer is the ip address of the peer this block was downloaded from.
|
||||
*
|
||||
* @return the peer tcp endpoint
|
||||
*/
|
||||
public TcpEndpoint peer() {
|
||||
return new TcpEndpoint(b.peer());
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes that have been received for this block.
|
||||
*
|
||||
* @return the number of bytes received
|
||||
*/
|
||||
public int bytesProgress() {
|
||||
return (int) b.getBytes_progress();
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of bytes in this block.
|
||||
*
|
||||
* @return otal number of bytes
|
||||
*/
|
||||
public int blockSize() {
|
||||
return (int) b.getBlock_size();
|
||||
}
|
||||
|
||||
/**
|
||||
* The state this block is in.
|
||||
*
|
||||
* @return the block's state
|
||||
*/
|
||||
public BlockState state() {
|
||||
return BlockState.fromSwig((int) b.getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of peers that is currently requesting this block. Typically
|
||||
* this is 0 or 1, but at the end of the torrent blocks may be requested
|
||||
* by more peers in parallel to speed things up.
|
||||
*
|
||||
* @return number of peers
|
||||
*/
|
||||
public int numPeers() {
|
||||
return (int) b.getNum_peers();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the enum used for {@link #state()}.
|
||||
*/
|
||||
public enum BlockState {
|
||||
|
||||
/**
|
||||
* This block has not been downloaded or requested form any peer.
|
||||
*/
|
||||
NONE(block_info.block_state_t.none.swigValue()),
|
||||
|
||||
/**
|
||||
* The block has been requested, but not completely downloaded yet.
|
||||
*/
|
||||
REQUESTED(block_info.block_state_t.requested.swigValue()),
|
||||
|
||||
/**
|
||||
* The block has been downloaded and is currently queued for being
|
||||
* written to disk.
|
||||
*/
|
||||
WRITING(block_info.block_state_t.writing.swigValue()),
|
||||
|
||||
/**
|
||||
* The block has been written to disk.
|
||||
*/
|
||||
FINISHED(block_info.block_state_t.finished.swigValue()),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UNKNOWN(-1);
|
||||
|
||||
BlockState(int swigValue) {
|
||||
this.swigValue = swigValue;
|
||||
}
|
||||
|
||||
private final int swigValue;
|
||||
|
||||
/**
|
||||
* @return the native value
|
||||
*/
|
||||
public int swig() {
|
||||
return swigValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param swigValue the native value
|
||||
* @return the state
|
||||
*/
|
||||
public static BlockState fromSwig(int swigValue) {
|
||||
BlockState[] enumValues = BlockState.class.getEnumConstants();
|
||||
for (BlockState ev : enumValues) {
|
||||
if (ev.swig() == swigValue) {
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.dht_lookup;
|
||||
|
||||
/**
|
||||
* Holds statistics about a current dht_lookup operation.
|
||||
* a DHT lookup is the traversal of nodes, looking up a
|
||||
* set of target nodes in the DHT for retrieving and possibly
|
||||
* storing information in the DHT
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class DhtLookup {
|
||||
|
||||
private final dht_lookup l;
|
||||
|
||||
/**
|
||||
* internal use
|
||||
*
|
||||
* @param l
|
||||
*/
|
||||
public DhtLookup(dht_lookup l) {
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* The native object.
|
||||
*
|
||||
* @return the native object
|
||||
*/
|
||||
public dht_lookup swig() {
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* string literal indicating which kind of lookup this is.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String type() {
|
||||
return l.get_type();
|
||||
}
|
||||
|
||||
/**
|
||||
* the number of outstanding request to individual nodes
|
||||
* this lookup has right now.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int outstandingRequests() {
|
||||
return l.getOutstanding_requests();
|
||||
}
|
||||
|
||||
/**
|
||||
* the total number of requests that have timed out so far
|
||||
* for this lookup.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int timeouts() {
|
||||
return l.getTimeouts();
|
||||
}
|
||||
|
||||
/**
|
||||
* the total number of responses we have received for this
|
||||
* lookup so far for this lookup.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int responses() {
|
||||
return l.getResponses();
|
||||
}
|
||||
|
||||
/**
|
||||
* the branch factor for this lookup. This is the number of
|
||||
* nodes we keep outstanding requests to in parallel by default.
|
||||
* when nodes time out we may increase this.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int branchFactor() {
|
||||
return l.getBranch_factor();
|
||||
}
|
||||
|
||||
/**
|
||||
* the number of nodes left that could be queries for this
|
||||
* lookup. Many of these are likely to be part of the trail
|
||||
* while performing the lookup and would never end up actually
|
||||
* being queried.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int nodesLeft() {
|
||||
return l.getNodes_left();
|
||||
}
|
||||
|
||||
/**
|
||||
* the number of seconds ago the
|
||||
* last message was sent that's still
|
||||
* outstanding.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int lastSent() {
|
||||
return l.getLast_sent();
|
||||
}
|
||||
|
||||
/**
|
||||
* the number of outstanding requests
|
||||
* that have exceeded the short timeout
|
||||
* and are considered timed out in the
|
||||
* sense that they increased the branch
|
||||
* factor.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int firstTimeout() {
|
||||
return l.getFirst_timeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* The node-id or info-hash target for this lookup.
|
||||
*
|
||||
* @return the target
|
||||
*/
|
||||
public Sha1Hash target() {
|
||||
return new Sha1Hash(l.getTarget());
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.dht_routing_bucket;
|
||||
|
||||
/**
|
||||
* Hold information about a single DHT routing table bucket
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class DhtRoutingBucket {
|
||||
|
||||
private final dht_routing_bucket t;
|
||||
|
||||
public DhtRoutingBucket(dht_routing_bucket t) {
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
public dht_routing_bucket swig() {
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of nodes in the routing table.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int numNodes() {
|
||||
return t.getNum_nodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of replacement nodes in the routing table.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int numReplacements() {
|
||||
return t.getNum_replacements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of seconds since last activity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int lastActive() {
|
||||
return t.getLast_active();
|
||||
}
|
||||
}
|
@ -0,0 +1,452 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.dht_settings;
|
||||
|
||||
/**
|
||||
* Structure used to hold configuration options for the DHT.
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class DhtSettings {
|
||||
|
||||
private final dht_settings s;
|
||||
|
||||
public DhtSettings(dht_settings s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
public DhtSettings() {
|
||||
this(new dht_settings());
|
||||
}
|
||||
|
||||
public dht_settings swig() {
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* the maximum number of peers to send in a reply to ``get_peers``.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int maxPeersReply() {
|
||||
return s.getMax_peers_reply();
|
||||
}
|
||||
|
||||
/**
|
||||
* the maximum number of peers to send in a reply to ``get_peers``.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void maxPeersReply(int value) {
|
||||
s.setMax_peers_reply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* the number of concurrent search request the node will send when
|
||||
* announcing and refreshing the routing table. This parameter is called
|
||||
* alpha in the kademlia paper.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getSearchBranching() {
|
||||
return s.getSearch_branching();
|
||||
}
|
||||
|
||||
/**
|
||||
* the number of concurrent search request the node will send when
|
||||
* announcing and refreshing the routing table. This parameter is called
|
||||
* alpha in the kademlia paper.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setSearchBranching(int value) {
|
||||
s.setSearch_branching(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* the maximum number of failed tries to contact a node before it is
|
||||
* removed from the routing table. If there are known working nodes that
|
||||
* are ready to replace a failing node, it will be replaced immediately,
|
||||
* this limit is only used to clear out nodes that don't have any node
|
||||
* that can replace them.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getMaxFailCount() {
|
||||
return s.getMax_fail_count();
|
||||
}
|
||||
|
||||
/**
|
||||
* the maximum number of failed tries to contact a node before it is
|
||||
* removed from the routing table. If there are known working nodes that
|
||||
* are ready to replace a failing node, it will be replaced immediately,
|
||||
* this limit is only used to clear out nodes that don't have any node
|
||||
* that can replace them.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setMaxFailCount(int value) {
|
||||
s.setMax_fail_count(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* the total number of torrents to track from the DHT. This is simply an
|
||||
* upper limit to make sure malicious DHT nodes cannot make us allocate
|
||||
* an unbounded amount of memory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int maxTorrents() {
|
||||
return s.getMax_torrents();
|
||||
}
|
||||
|
||||
/**
|
||||
* the total number of torrents to track from the DHT. This is simply an
|
||||
* upper limit to make sure malicious DHT nodes cannot make us allocate
|
||||
* an unbounded amount of memory.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void maxTorrents(int value) {
|
||||
s.setMax_torrents(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* max number of items the DHT will store.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int maxDhtItems() {
|
||||
return s.getMax_dht_items();
|
||||
}
|
||||
|
||||
/**
|
||||
* max number of items the DHT will store.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void maxDhtItems(int value) {
|
||||
s.setMax_dht_items(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The max number of peers to store per torrent (for the DHT).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int maxPeers() {
|
||||
return s.getMax_peers();
|
||||
}
|
||||
|
||||
/**
|
||||
* The max number of peers to store per torrent (for the DHT).
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void maxPeers(int value) {
|
||||
s.setMax_peers(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* the max number of torrents to return in a torrent search query to the
|
||||
* DHT.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getMaxTorrentSearchReply() {
|
||||
return s.getMax_torrent_search_reply();
|
||||
}
|
||||
|
||||
/**
|
||||
* the max number of torrents to return in a torrent search query to the
|
||||
* DHT.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setMaxTorrentSearchReply(int value) {
|
||||
s.setMax_torrent_search_reply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* determines if the routing table entries should restrict entries to one
|
||||
* per IP. This defaults to true, which helps mitigate some attacks on
|
||||
* the DHT. It prevents adding multiple nodes with IPs with a very close
|
||||
* CIDR distance.
|
||||
* <p>
|
||||
* when set, nodes whose IP address that's in the same /24 (or /64 for
|
||||
* IPv6) range in the same routing table bucket. This is an attempt to
|
||||
* mitigate node ID spoofing attacks also restrict any IP to only have a
|
||||
* single entry in the whole routing table.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRestrictRoutingIPs() {
|
||||
return s.getRestrict_routing_ips();
|
||||
}
|
||||
|
||||
/**
|
||||
* determines if the routing table entries should restrict entries to one
|
||||
* per IP. This defaults to true, which helps mitigate some attacks on
|
||||
* the DHT. It prevents adding multiple nodes with IPs with a very close
|
||||
* CIDR distance.
|
||||
* <p>
|
||||
* when set, nodes whose IP address that's in the same /24 (or /64 for
|
||||
* IPv6) range in the same routing table bucket. This is an attempt to
|
||||
* mitigate node ID spoofing attacks also restrict any IP to only have a
|
||||
* single entry in the whole routing table.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setRestrictRoutingIPs(boolean value) {
|
||||
s.setRestrict_routing_ips(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* determines if DHT searches should prevent adding nodes with IPs with
|
||||
* very close CIDR distance. This also defaults to true and helps
|
||||
* mitigate certain attacks on the DHT.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRestrictSearchIPs() {
|
||||
return s.getRestrict_search_ips();
|
||||
}
|
||||
|
||||
/**
|
||||
* determines if DHT searches should prevent adding nodes with IPs with
|
||||
* very close CIDR distance. This also defaults to true and helps
|
||||
* mitigate certain attacks on the DHT.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setRestrictSearchIPs(boolean value) {
|
||||
s.setRestrict_search_ips(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* makes the first buckets in the DHT routing table fit 128, 64, 32 and
|
||||
* 16 nodes respectively, as opposed to the standard size of 8. All other
|
||||
* buckets have size 8 still.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isExtendedRoutingTable() {
|
||||
return s.getExtended_routing_table();
|
||||
}
|
||||
|
||||
/**
|
||||
* makes the first buckets in the DHT routing table fit 128, 64, 32 and
|
||||
* 16 nodes respectively, as opposed to the standard size of 8. All other
|
||||
* buckets have size 8 still.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setExtendedRoutingTable(boolean value) {
|
||||
s.setExtended_routing_table(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* slightly changes the lookup behavior in terms of how many outstanding
|
||||
* requests we keep. Instead of having branch factor be a hard limit, we
|
||||
* always keep *branch factor* outstanding requests to the closest nodes.
|
||||
* i.e. every time we get results back with closer nodes, we query them
|
||||
* right away. It lowers the lookup times at the cost of more outstanding
|
||||
* queries.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAggressiveLookups() {
|
||||
return s.getAggressive_lookups();
|
||||
}
|
||||
|
||||
/**
|
||||
* slightly changes the lookup behavior in terms of how many outstanding
|
||||
* requests we keep. Instead of having branch factor be a hard limit, we
|
||||
* always keep *branch factor* outstanding requests to the closest nodes.
|
||||
* i.e. every time we get results back with closer nodes, we query them
|
||||
* right away. It lowers the lookup times at the cost of more outstanding
|
||||
* queries.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void getAggressiveLookups(boolean value) {
|
||||
s.setAggressive_lookups(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* when set, perform lookups in a way that is slightly more expensive,
|
||||
* but which minimizes the amount of information leaked about you.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isPrivacyLookups() {
|
||||
return s.getPrivacy_lookups();
|
||||
}
|
||||
|
||||
/**
|
||||
* when set, perform lookups in a way that is slightly more expensive,
|
||||
* but which minimizes the amount of information leaked about you.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setPrivacyLookups(boolean value) {
|
||||
s.setPrivacy_lookups(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* when set, node's whose IDs that are not correctly generated based on
|
||||
* its external IP are ignored. When a query arrives from such node, an
|
||||
* error message is returned with a message saying "invalid node ID".
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isEnforceNodeId() {
|
||||
return s.getEnforce_node_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* when set, node's whose IDs that are not correctly generated based on
|
||||
* its external IP are ignored. When a query arrives from such node, an
|
||||
* error message is returned with a message saying "invalid node ID".
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setEnforceNodeId(boolean value) {
|
||||
s.setEnforce_node_id(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* ignore DHT messages from parts of the internet we wouldn't expect to
|
||||
* see any traffic from
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isIgnoreDarkInternet() {
|
||||
return s.getIgnore_dark_internet();
|
||||
}
|
||||
|
||||
/**
|
||||
* ignore DHT messages from parts of the internet we wouldn't expect to
|
||||
* see any traffic from
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setIgnoreDarkInternet(boolean value) {
|
||||
s.setIgnore_dark_internet(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of seconds a DHT node is banned if it exceeds the rate
|
||||
* limit. The rate limit is averaged over 10 seconds to allow for bursts
|
||||
* above the limit.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int blockTimeout() {
|
||||
return s.getBlock_timeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of seconds a DHT node is banned if it exceeds the rate
|
||||
* limit. The rate limit is averaged over 10 seconds to allow for bursts
|
||||
* above the limit.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void blockTimeout(int value) {
|
||||
s.setBlock_timeout(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The max number of packets per second a DHT node is allowed to send
|
||||
* without getting banned.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int blockRatelimit() {
|
||||
return s.getBlock_ratelimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* The max number of packets per second a DHT node is allowed to send
|
||||
* without getting banned.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void blockRatelimit(int value) {
|
||||
s.setBlock_ratelimit(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* When set, the other nodes won't keep this node in their routing
|
||||
* tables, it's meant for low-power and/or ephemeral devices that
|
||||
* cannot support the DHT, it is also useful for mobile devices which
|
||||
* are sensitive to network traffic and battery life.
|
||||
* this node no longer responds to 'query' messages, and will place a
|
||||
* 'ro' key (value = 1) in the top-level message dictionary of outgoing
|
||||
* query messages.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean readOnly() {
|
||||
return s.getRead_only();
|
||||
}
|
||||
|
||||
/**
|
||||
* When set, the other nodes won't keep this node in their routing
|
||||
* tables, it's meant for low-power and/or ephemeral devices that
|
||||
* cannot support the DHT, it is also useful for mobile devices which
|
||||
* are sensitive to network traffic and battery life.
|
||||
* this node no longer responds to 'query' messages, and will place a
|
||||
* 'ro' key (value = 1) in the top-level message dictionary of outgoing
|
||||
* query messages.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void readOnly(boolean value) {
|
||||
s.setRead_only(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of seconds a immutable/mutable item will be expired.
|
||||
* default is 0, means never expires.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int itemLifetime() {
|
||||
return s.getItem_lifetime();
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of seconds a immutable/mutable item will be expired.
|
||||
* default is 0, means never expires.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void itemLifetime(int value) {
|
||||
s.setItem_lifetime(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes per second (on average) the DHT is allowed to send.
|
||||
* If the incoming requests causes to many bytes to be sent in responses,
|
||||
* incoming requests will be dropped until the quota has been replenished.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int uploadRateLimit() {
|
||||
return s.getUpload_rate_limit();
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes per second (on average) the DHT is allowed to send.
|
||||
* If the incoming requests causes to many bytes to be sent in responses,
|
||||
* incoming requests will be dropped until the quota has been replenished.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void uploadRateLimit(int value) {
|
||||
s.setUpload_rate_limit(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.byte_vector;
|
||||
import com.frostwire.jlibtorrent.swig.byte_vectors_pair;
|
||||
|
||||
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_add_scalar_public;
|
||||
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_add_scalar_secret;
|
||||
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_create_keypair;
|
||||
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_create_seed;
|
||||
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_key_exchange;
|
||||
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_sign;
|
||||
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_verify;
|
||||
|
||||
/**
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class Ed25519 {
|
||||
|
||||
public final static int SEED_SIZE = 32;
|
||||
public final static int PUBLIC_KEY_SIZE = 32;
|
||||
public final static int SECRET_KEY_SIZE = 64;
|
||||
public final static int SIGNATURE_SIZE = 64;
|
||||
public final static int SCALAR_SIZE = 32;
|
||||
public final static int SHARED_SECRET_SIZE = 32;
|
||||
|
||||
private Ed25519() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public static byte[] createSeed() {
|
||||
byte_vector seed = ed25519_create_seed();
|
||||
|
||||
return Vectors.byte_vector2bytes(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param seed
|
||||
* @return
|
||||
*/
|
||||
public static Pair<byte[], byte[]> createKeypair(byte[] seed) {
|
||||
if (seed == null || seed.length != SEED_SIZE) {
|
||||
throw new IllegalArgumentException("seed must be not null and of size " + SEED_SIZE);
|
||||
}
|
||||
|
||||
byte_vectors_pair keypair = ed25519_create_keypair(Vectors.bytes2byte_vector(seed));
|
||||
|
||||
return new Pair<>(Vectors.byte_vector2bytes(keypair.getFirst()),
|
||||
Vectors.byte_vector2bytes(keypair.getSecond()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message
|
||||
* @param publicKey
|
||||
* @param secretKey
|
||||
* @return
|
||||
*/
|
||||
public static byte[] sign(byte[] message, byte[] publicKey, byte[] secretKey) {
|
||||
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
|
||||
}
|
||||
if (secretKey == null || secretKey.length != SECRET_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("secret key must be not null and of size " + SECRET_KEY_SIZE);
|
||||
}
|
||||
|
||||
byte_vector signature = ed25519_sign(Vectors.bytes2byte_vector(message),
|
||||
Vectors.bytes2byte_vector(publicKey),
|
||||
Vectors.bytes2byte_vector(secretKey));
|
||||
|
||||
return Vectors.byte_vector2bytes(signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param signature
|
||||
* @param message
|
||||
* @param publicKey
|
||||
* @return
|
||||
*/
|
||||
public static boolean verify(byte[] signature, byte[] message, byte[] publicKey) {
|
||||
if (signature == null || signature.length != SIGNATURE_SIZE) {
|
||||
throw new IllegalArgumentException("signature must be not null and of size " + SIGNATURE_SIZE);
|
||||
}
|
||||
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
|
||||
}
|
||||
return ed25519_verify(Vectors.bytes2byte_vector(signature),
|
||||
Vectors.bytes2byte_vector(message), Vectors.bytes2byte_vector(publicKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param publicKey
|
||||
* @param scalar
|
||||
* @return
|
||||
*/
|
||||
public static byte[] addScalarPublic(byte[] publicKey, byte[] scalar) {
|
||||
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
|
||||
}
|
||||
if (scalar == null || scalar.length != SCALAR_SIZE) {
|
||||
throw new IllegalArgumentException("scalar must be not null and of size " + SCALAR_SIZE);
|
||||
}
|
||||
|
||||
byte_vector ret = ed25519_add_scalar_public(Vectors.bytes2byte_vector(publicKey), Vectors.bytes2byte_vector(scalar));
|
||||
|
||||
return Vectors.byte_vector2bytes(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secretKey
|
||||
* @param scalar
|
||||
* @return
|
||||
*/
|
||||
public static byte[] addScalarSecret(byte[] secretKey, byte[] scalar) {
|
||||
if (secretKey == null || secretKey.length != SECRET_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("public key must be not null and of size " + SECRET_KEY_SIZE);
|
||||
}
|
||||
if (scalar == null || scalar.length != SCALAR_SIZE) {
|
||||
throw new IllegalArgumentException("scalar must be not null and of size " + SCALAR_SIZE);
|
||||
}
|
||||
|
||||
byte_vector ret = ed25519_add_scalar_secret(Vectors.bytes2byte_vector(secretKey), Vectors.bytes2byte_vector(scalar));
|
||||
|
||||
return Vectors.byte_vector2bytes(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param publicKey
|
||||
* @param secretKey
|
||||
* @return
|
||||
*/
|
||||
public byte[] keyExchange(byte[] publicKey, byte[] secretKey) {
|
||||
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
|
||||
}
|
||||
if (secretKey == null || secretKey.length != SECRET_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("private key must be not null and of size " + SECRET_KEY_SIZE);
|
||||
}
|
||||
|
||||
byte_vector secret = ed25519_key_exchange(Vectors.bytes2byte_vector(publicKey),
|
||||
Vectors.bytes2byte_vector(secretKey));
|
||||
|
||||
return Vectors.byte_vector2bytes(secret);
|
||||
}
|
||||
}
|
@ -0,0 +1,226 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.entry;
|
||||
import com.frostwire.jlibtorrent.swig.entry_vector;
|
||||
import com.frostwire.jlibtorrent.swig.string_entry_map;
|
||||
import com.frostwire.jlibtorrent.swig.string_vector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.AbstractList;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The Entry class represents one node in a bencoded hierarchy. It works as a
|
||||
* variant type, it can be either a list, a dictionary, an integer
|
||||
* or a string.
|
||||
*
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class Entry {
|
||||
|
||||
private final entry e;
|
||||
|
||||
public Entry(entry e) {
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
public Entry(String s) {
|
||||
this(new entry(s));
|
||||
}
|
||||
|
||||
public Entry(long n) {
|
||||
this(new entry(n));
|
||||
}
|
||||
|
||||
public entry swig() {
|
||||
return e;
|
||||
}
|
||||
|
||||
public byte[] bencode() {
|
||||
return Vectors.byte_vector2bytes(e.bencode());
|
||||
}
|
||||
|
||||
public String string() {
|
||||
return e.string();
|
||||
}
|
||||
|
||||
public long integer() {
|
||||
return e.integer();
|
||||
}
|
||||
|
||||
public List<Entry> list() {
|
||||
return new EntryList(e.list());
|
||||
}
|
||||
|
||||
public Map<String, Entry> dictionary() {
|
||||
return new EntryMap(e.dict());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return e.to_string();
|
||||
}
|
||||
|
||||
public static Entry bdecode(byte[] data) {
|
||||
return new Entry(entry.bdecode(Vectors.bytes2byte_vector(data)));
|
||||
}
|
||||
|
||||
public static Entry bdecode(File file) throws IOException {
|
||||
byte[] data = Files.bytes(file);
|
||||
return bdecode(data);
|
||||
}
|
||||
|
||||
public static Entry fromList(List<?> list) {
|
||||
entry e = new entry(entry.data_type.list_t);
|
||||
|
||||
entry_vector d = e.list();
|
||||
for (Object v : list) {
|
||||
if (v instanceof String) {
|
||||
d.push_back(new entry((String) v));
|
||||
} else if (v instanceof Integer) {
|
||||
d.push_back(new entry((Integer) v));
|
||||
} else if (v instanceof Entry) {
|
||||
d.push_back(((Entry) v).swig());
|
||||
} else if (v instanceof entry) {
|
||||
d.push_back((entry) v);
|
||||
} else if (v instanceof List) {
|
||||
d.push_back(fromList((List<?>) v).swig());
|
||||
} else if (v instanceof Map) {
|
||||
d.push_back(fromMap((Map<String, ?>) v).swig());
|
||||
} else {
|
||||
d.push_back(new entry(v.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
return new Entry(e);
|
||||
}
|
||||
|
||||
public static Entry fromMap(Map<String, ?> map) {
|
||||
entry e = new entry(entry.data_type.dictionary_t);
|
||||
|
||||
string_entry_map d = e.dict();
|
||||
for (String k : map.keySet()) {
|
||||
Object v = map.get(k);
|
||||
|
||||
if (v instanceof String) {
|
||||
d.set(k, new entry((String) v));
|
||||
} else if (v instanceof Integer) {
|
||||
d.set(k, new entry((Integer) v));
|
||||
} else if (v instanceof Entry) {
|
||||
d.set(k, ((Entry) v).swig());
|
||||
} else if (v instanceof entry) {
|
||||
d.set(k, (entry) v);
|
||||
} else if (v instanceof List) {
|
||||
d.set(k, fromList((List<?>) v).swig());
|
||||
} else if (v instanceof Map) {
|
||||
d.set(k, fromMap((Map<String, ?>) v).swig());
|
||||
} else {
|
||||
d.set(k, new entry(v.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
return new Entry(e);
|
||||
}
|
||||
|
||||
private static final class EntryList extends AbstractList<Entry> {
|
||||
|
||||
private final entry_vector v;
|
||||
|
||||
public EntryList(entry_vector v) {
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry get(int index) {
|
||||
return new Entry(v.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Entry entry) {
|
||||
v.push_back(entry.swig());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return (int) v.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
v.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return v.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EntryMap extends AbstractMap<String, Entry> {
|
||||
|
||||
private final string_entry_map m;
|
||||
|
||||
public EntryMap(string_entry_map m) {
|
||||
this.m = m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.frostwire.jlibtorrent.Entry get(Object key) {
|
||||
String k = key.toString();
|
||||
return m.has_key(k) ? new com.frostwire.jlibtorrent.Entry(m.get(key.toString())) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.frostwire.jlibtorrent.Entry put(String key, com.frostwire.jlibtorrent.Entry value) {
|
||||
com.frostwire.jlibtorrent.Entry r = get(key);
|
||||
m.set(key, value.swig());
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return (int) m.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
m.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return m.has_key(key.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return m.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
HashSet<String> s = new HashSet<>();
|
||||
|
||||
string_vector v = m.keys();
|
||||
int size = (int) v.size();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
s.add(v.get(i));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, com.frostwire.jlibtorrent.Entry>> entrySet() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
|
||||
*
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.ip_interface;
|
||||
import com.frostwire.jlibtorrent.swig.ip_interface_vector;
|
||||
import com.frostwire.jlibtorrent.swig.ip_route;
|
||||
import com.frostwire.jlibtorrent.swig.ip_route_vector;
|
||||
import com.frostwire.jlibtorrent.swig.libtorrent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class EnumNet {
|
||||
|
||||
private EnumNet() {
|
||||
}
|
||||
|
||||
public static List<IpInterface> enumInterfaces(SessionManager session) {
|
||||
if (session.swig() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ip_interface_vector v = libtorrent.enum_net_interfaces(session.swig());
|
||||
int size = (int) v.size();
|
||||
ArrayList<IpInterface> l = new ArrayList<>(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(new IpInterface(v.get(i)));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
public static List<IpRoute> enumRoutes(SessionManager session) {
|
||||
if (session.swig() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ip_route_vector v = libtorrent.enum_routes(session.swig());
|
||||
int size = (int) v.size();
|
||||
ArrayList<IpRoute> l = new ArrayList<>(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
l.add(new IpRoute(v.get(i)));
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
public static Address getGateway(SessionManager session, IpInterface ipInterface, ip_route_vector routes) {
|
||||
return new Address(libtorrent.get_gateway(ipInterface.swig(), routes));
|
||||
}
|
||||
|
||||
public static final class IpInterface {
|
||||
|
||||
private final Address interfaceAddress;
|
||||
private final Address netmask;
|
||||
private final String name;
|
||||
private final String friendlyName;
|
||||
private final String description;
|
||||
private final boolean preferred;
|
||||
private final ip_interface s;
|
||||
|
||||
IpInterface(ip_interface iface) {
|
||||
this.s = iface;
|
||||
this.interfaceAddress = new Address(iface.getInterface_address());
|
||||
this.netmask = new Address(iface.getNetmask());
|
||||
this.name = Vectors.byte_vector2ascii(iface.getName());
|
||||
this.friendlyName = Vectors.byte_vector2ascii(iface.getFriendly_name());
|
||||
this.description = Vectors.byte_vector2ascii(iface.getDescription());
|
||||
this.preferred = iface.getPreferred();
|
||||
}
|
||||
|
||||
public ip_interface swig() {
|
||||
return s;
|
||||
}
|
||||
|
||||
public Address interfaceAddress() {
|
||||
return interfaceAddress;
|
||||
}
|
||||
|
||||
public Address netmask() {
|
||||
return netmask;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String friendlyName() {
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean preferred() {
|
||||
return preferred;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("address: ").append(interfaceAddress);
|
||||
sb.append(", netmask: ").append(netmask);
|
||||
sb.append(", name: ").append(name);
|
||||
sb.append(", friendlyName: ").append(friendlyName);
|
||||
sb.append(", description: ").append(description);
|
||||
sb.append(", preferred: ").append(preferred);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class IpRoute {
|
||||
|
||||
private final Address destination;
|
||||
private final Address netmask;
|
||||
private final Address gateway;
|
||||
private final String name;
|
||||
private final int mtu;
|
||||
private final ip_route s;
|
||||
|
||||
IpRoute(ip_route route) {
|
||||
this.s = route;
|
||||
this.destination = new Address(route.getDestination());
|
||||
this.netmask = new Address(route.getNetmask());
|
||||
this.gateway = new Address(route.getGateway());
|
||||
this.name = Vectors.byte_vector2ascii(route.getName());
|
||||
this.mtu = route.getMtu();
|
||||
}
|
||||
|
||||
public ip_route swig() {
|
||||
return this.s;
|
||||
}
|
||||
|
||||
public Address destination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public Address netmask() {
|
||||
return netmask;
|
||||
}
|
||||
|
||||
public Address gateway() {
|
||||
return gateway;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int mtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("destination: ").append(destination);
|
||||
sb.append(", netmask: ").append(netmask);
|
||||
sb.append(", gateway: ").append(gateway);
|
||||
sb.append(", name: ").append(name);
|
||||
sb.append(", mtu: ").append(mtu);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.frostwire.jlibtorrent;
|
||||
|
||||
import com.frostwire.jlibtorrent.swig.error_code;
|
||||
|
||||
/**
|
||||
* @author gubatron
|
||||
* @author aldenml
|
||||
*/
|
||||
public final class ErrorCode {
|
||||
|
||||
private int value;
|
||||
private String message;
|
||||
private boolean isError;
|
||||
|
||||
/**
|
||||
* @param ec the native object
|
||||
*/
|
||||
public ErrorCode(error_code ec) {
|
||||
assign(ec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the internal error code value
|
||||
*/
|
||||
public int value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the error message
|
||||
*/
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this error code actually represents an error.
|
||||
*
|
||||
* @return true if an actual error
|
||||
*/
|
||||
public boolean isError() {
|
||||
return isError;
|
||||
}
|
||||
|
||||
void assign(error_code ec) {
|
||||
value = ec.value();
|
||||
message = ec.message();
|
||||
isError = ec.op_bool();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user