Merge pull request #32 from sschueller/develop

Release 1.0.4
This commit is contained in:
Stefan Schüller 2018-12-02 14:49:57 +01:00 committed by GitHub
commit 1179f75677
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 567 additions and 392 deletions

View File

@ -6,9 +6,52 @@ android {
applicationId "net.schueller.peertube" applicationId "net.schueller.peertube"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 28
versionCode 103 versionCode 104
versionName "1.0.3" versionName "1.0.4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Layouts and design
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.android.support:support-v13:28.0.0'
// font awesome
implementation 'com.blackboardtheory:android-iconify-fontawesome:3.0.1-SNAPSHOT'
// BottomNavigationViewEx -> https://github.com/ittianyu/BottomNavigationViewEx
implementation 'com.github.ittianyu:BottomNavigationViewEx:2.0.2'
// http client / REST
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
// image downloading and caching library
implementation 'com.squareup.picasso:picasso:2.5.2'
// json decoder/encoder
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
// Torrents and WebRTC
implementation 'com.github.TorrentStream:TorrentStream-Android:2.5.0'
// implementation "com.github.TorrentStream:TorrentStreamServer-Android:1.0.1"
// implementation 'org.webrtc:google-webrtc:1.0.+'
// video player
implementation 'com.google.android.exoplayer:exoplayer:2.8.1'
// implementation 'com.devbrackets.android:exomedia:4.1.0'
// testing
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
} }
buildTypes { buildTypes {
release { release {
@ -26,40 +69,3 @@ android {
} }
} }
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.google.code.gson:gson:2.8.2'
// implementation 'org.webrtc:google-webrtc:1.0.+'
implementation 'com.android.support:design:28.0.0'
// BottomNavigationViewEx -> https://github.com/ittianyu/BottomNavigationViewEx
implementation 'com.github.ittianyu:BottomNavigationViewEx:2.0.2'
implementation 'com.blackboardtheory:android-iconify-fontawesome:3.0.1-SNAPSHOT'
implementation 'com.github.TorrentStream:TorrentStream-Android:2.5.0'
implementation 'com.google.android.exoplayer:exoplayer:2.8.1'
// implementation "com.github.TorrentStream:TorrentStreamServer-Android:1.0.1"
// implementation 'com.devbrackets.android:exomedia:4.1.0'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.android.support:support-v4:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View File

@ -10,28 +10,23 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
tools:ignore="GoogleAppIndexingWarning"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<meta-data android:name="android.app.default_searchable" <meta-data
android:name="android.app.default_searchable"
android:value=".activity.SearchActivity" /> android:value=".activity.SearchActivity" />
<activity <activity android:name=".activity.VideoListActivity">
android:name=".activity.VideoListActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activity.SearchActivity" android:name=".activity.SearchActivity"
android:label="@string/title_activity_search" android:label="@string/title_activity_search"
@ -44,7 +39,6 @@
android:name="android.app.searchable" android:name="android.app.searchable"
android:resource="@xml/searchable" /> android:resource="@xml/searchable" />
</activity> </activity>
<activity <activity
android:name=".activity.LoginActivity" android:name=".activity.LoginActivity"
android:label="@string/title_activity_login" /> android:label="@string/title_activity_login" />
@ -59,10 +53,12 @@
android:label="@string/title_activity_settings" /> android:label="@string/title_activity_settings" />
<!-- Content provider for search suggestions --> <!-- Content provider for search suggestions -->
<provider android:name=".provider.SearchSuggestionsProvider" <provider
android:enabled="true" android:name=".provider.SearchSuggestionsProvider"
android:authorities="net.schueller.peertube.provider.SearchSuggestionsProvider" android:authorities="net.schueller.peertube.provider.SearchSuggestionsProvider"
android:enabled="true"
android:exported="false" /> android:exported="false" />
</application>
<activity android:name=".activity.SelectServerActivity"/>
</application>
</manifest> </manifest>

View File

@ -37,9 +37,9 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
return getDelegate().getSupportActionBar(); return getDelegate().getSupportActionBar();
} }
public void setSupportActionBar(@Nullable Toolbar toolbar) { // public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar); // getDelegate().setSupportActionBar(toolbar);
} // }
@Override @Override
public MenuInflater getMenuInflater() { public MenuInflater getMenuInflater() {

View File

@ -1,57 +1,43 @@
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
import android.animation.Animator; import android.os.StrictMode;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.ContactsContract; import android.util.Log;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.model.OauthClient;
import net.schueller.peertube.model.Token;
import net.schueller.peertube.model.VideoList;
import net.schueller.peertube.network.AuthenticationService;
import net.schueller.peertube.network.GetVideoDataService;
import net.schueller.peertube.network.RetrofitInstance;
import static android.Manifest.permission.READ_CONTACTS; import java.io.IOException;
/** import okhttp3.FormBody;
* A login screen that offers login via email/password. import okhttp3.MediaType;
*/ import okhttp3.OkHttpClient;
public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> { import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import retrofit2.Call;
import retrofit2.Callback;
public class LoginActivity extends AppCompatActivity {
OkHttpClient client = new OkHttpClient();
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private String TAG = "LoginActivity";
/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
private static final String[] DUMMY_CREDENTIALS = new String[]{
"foo@example.com:hello", "bar@example.com:world"
};
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;
// UI references. // UI references.
private AutoCompleteTextView mEmailView; private AutoCompleteTextView mEmailView;
@ -63,43 +49,25 @@ public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login); setContentView(R.layout.activity_login);
// Set up the login form.
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
mPasswordView = (EditText) findViewById(R.id.password); // bind button click
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { Button mEmailSignInButton = findViewById(R.id.email_sign_in_button);
@Override mEmailSignInButton.setOnClickListener(view -> attemptLogin());
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button); mEmailView = findViewById(R.id.email);
mEmailSignInButton.setOnClickListener(new OnClickListener() { mPasswordView = findViewById(R.id.password);
@Override
public void onClick(View view) { // if (android.os.Build.VERSION.SDK_INT > 9) {
attemptLogin(); // StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
} // StrictMode.setThreadPolicy(policy);
}); // }
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
} }
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() { private void attemptLogin() {
if (mAuthTask != null) {
return;
}
// Reset errors. // Reset errors.
mEmailView.setError(null); mEmailView.setError(null);
@ -109,195 +77,60 @@ public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<
String email = mEmailView.getText().toString(); String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString(); String password = mPasswordView.getText().toString();
boolean cancel = false; // make http call to login and save access tokens
View focusView = null;
// Check for a valid password, if the user entered one. String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address. AuthenticationService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(AuthenticationService.class);
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) { Call<OauthClient> call = service.getOauthClientLocal();
// There was an error; don't attempt login and focus the first call.enqueue(new Callback<OauthClient>() {
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mAuthTask = new UserLoginTask(email, password);
mAuthTask.execute((Void) null);
}
}
private boolean isEmailValid(String email) {
//TODO: Replace this with your own logic
return email.contains("@");
}
private boolean isPasswordValid(String password) {
//TODO: Replace this with your own logic
return password.length() > 4;
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onResponse(@NonNull Call<OauthClient> call, @NonNull retrofit2.Response<OauthClient> response) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
if (response.body() != null) {
Call<Token> call2 = service.getAuthenticationToken(
response.body().getClientId(),
response.body().getClientSecret(),
"code",
"password",
"upload",
email,
password
);
call2.enqueue(new Callback<Token>() {
@Override
public void onResponse(@NonNull Call<Token> call2, @NonNull retrofit2.Response<Token> response2) {
if (response2.body() != null) {
Log.wtf(TAG, response2.body().getAccessToken());
Log.wtf(TAG, response2.body().getExpiresIn());
Log.wtf(TAG, response2.body().getRefreshToken());
Log.wtf(TAG, response2.body().getTokenType());
} else {
Log.wtf(TAG, response2.toString());
}
}
@Override
public void onFailure(@NonNull Call<Token> call2, @NonNull Throwable t2) {
Log.wtf("err", t2.fillInStackTrace());
} }
}); });
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); } else {
mProgressView.animate().setDuration(shortAnimTime).alpha( Log.wtf(TAG, response.toString());
show ? 1 : 0).setListener(new AnimatorListenerAdapter() { }
}
@Override @Override
public void onAnimationEnd(Animator animation) { public void onFailure(@NonNull Call<OauthClient> call, @NonNull Throwable t) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); Log.wtf("err", t.fillInStackTrace());
} }
}); });
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},
// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
List<String> emails = new ArrayList<>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
emails.add(cursor.getString(ProfileQuery.ADDRESS));
cursor.moveToNext();
}
addEmailsToAutoComplete(emails);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
ArrayAdapter<String> adapter =
new ArrayAdapter<>(LoginActivity.this,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
mEmailView.setAdapter(adapter);
} }
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};
int ADDRESS = 0;
int IS_PRIMARY = 1;
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
private final String mEmail;
private final String mPassword;
UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}
@Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
Thread.sleep(2000);
} catch (InterruptedException e) {
return false;
}
for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword);
}
}
// TODO: register the new account here.
return true;
}
@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
showProgress(false);
if (success) {
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}
} }

View File

@ -11,10 +11,10 @@ import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.adapter.VideoAdapter; import net.schueller.peertube.adapter.VideoAdapter;
import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.APIUrlHelper;
@ -117,9 +117,9 @@ public class SearchActivity extends AppCompatActivity {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false"; String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false";
String apiBaseURL = APIUrlHelper.getUrl(this); String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL + "/api/v1/").create(GetVideoDataService.class); GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
Call<VideoList> call = service.searchVideosData(start, count, sort, nsfw, search); Call<VideoList> call = service.searchVideosData(start, count, sort, nsfw, search);

View File

@ -0,0 +1,43 @@
package net.schueller.peertube.activity;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import net.schueller.peertube.R;
import net.schueller.peertube.model.ServerList;
import net.schueller.peertube.network.GetServerListDataService;
import net.schueller.peertube.network.RetrofitInstance;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class SelectServerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_server);
// get list of peertube servers
// TODO: Get here via settings, get data from API, add to adapter and show in recycle view, upon selection fill settings field
GetServerListDataService service = RetrofitInstance.getRetrofitInstance("https://instances.joinpeertube.org/api/v1/").create(GetServerListDataService.class);
Call<ServerList> call = service.getInstancesData(0, 500);
call.enqueue(new Callback<ServerList>() {
@Override
public void onResponse(@NonNull Call<ServerList> call, @NonNull Response<ServerList> response) {
// response.body().getVideoArrayList();
}
@Override
public void onFailure(@NonNull Call<ServerList> call, @NonNull Throwable t) {
}
});
}
}

View File

@ -4,19 +4,14 @@ import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.view.MenuItem; import android.view.MenuItem;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import java.util.List; import java.util.List;

View File

@ -8,7 +8,6 @@ import android.content.pm.PackageManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
@ -22,10 +21,10 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException; //import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException; //import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.common.GooglePlayServicesUtil; //import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.security.ProviderInstaller; //import com.google.android.gms.security.ProviderInstaller;
import com.ittianyu.bottomnavigationviewex.BottomNavigationViewEx; import com.ittianyu.bottomnavigationviewex.BottomNavigationViewEx;
import com.joanzapata.iconify.IconDrawable; import com.joanzapata.iconify.IconDrawable;
import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.Iconify;
@ -95,6 +94,9 @@ public class VideoListActivity extends AppCompatActivity {
//Log.v(TAG, "navigation_account"); //Log.v(TAG, "navigation_account");
Toast.makeText(VideoListActivity.this, "Account Not Implemented", Toast.LENGTH_SHORT).show(); Toast.makeText(VideoListActivity.this, "Account Not Implemented", Toast.LENGTH_SHORT).show();
// Intent intent = new Intent(this, LoginActivity.class);
// this.startActivity(intent);
return false; return false;
} }
return false; return false;
@ -116,14 +118,14 @@ public class VideoListActivity extends AppCompatActivity {
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
// fix android trying to use SSLv3 for handshake // fix android trying to use SSLv3 for handshake
updateAndroidSecurityProvider(this); // updateAndroidSecurityProvider(this);
// Bottom Navigation // Bottom Navigation
BottomNavigationViewEx navigation = findViewById(R.id.navigation); BottomNavigationViewEx navigation = findViewById(R.id.navigation);
navigation.enableAnimation(false); navigation.enableAnimation(false);
navigation.enableShiftingMode(false); navigation.setLabelVisibilityMode(1); // enableShiftingMode
navigation.enableItemShiftingMode(false); navigation.setItemHorizontalTranslationEnabled(false); // enableItemShiftingMode
Menu navMenu = navigation.getMenu(); Menu navMenu = navigation.getMenu();
navMenu.findItem(R.id.navigation_home).setIcon( navMenu.findItem(R.id.navigation_home).setIcon(
@ -212,7 +214,7 @@ public class VideoListActivity extends AppCompatActivity {
if (dy > 0) { if (dy > 0) {
// is at end of list? // is at end of list?
if(!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN)){ if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN)) {
if (!isLoading) { if (!isLoading) {
currentStart = currentStart + count; currentStart = currentStart + count;
loadVideos(currentStart, count, sort, filter); loadVideos(currentStart, count, sort, filter);
@ -240,9 +242,9 @@ public class VideoListActivity extends AppCompatActivity {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false"; String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false";
String apiBaseURL = APIUrlHelper.getUrl(this); String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL + "/api/v1/").create(GetVideoDataService.class); GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
Call<VideoList> call = service.getVideosData(start, count, sort, nsfw); Call<VideoList> call = service.getVideosData(start, count, sort, nsfw);
@ -277,26 +279,27 @@ public class VideoListActivity extends AppCompatActivity {
/** /**
* Force android to not use SSLv3 * Force android to not use SSLv3
* * <p>
* @param callingActivity Activity * // * @param callingActivity Activity
*/ */
private void updateAndroidSecurityProvider(Activity callingActivity) { // private void updateAndroidSecurityProvider(Activity callingActivity) {
try { // try {
ProviderInstaller.installIfNeeded(this); // ProviderInstaller.installIfNeeded(this);
} catch (GooglePlayServicesRepairableException e) { // } catch (GooglePlayServicesRepairableException e) {
// Thrown when Google Play Services is not installed, up-to-date, or enabled // // Thrown when Google Play Services is not installed, up-to-date, or enabled
// Show dialog to allow users to install, update, or otherwise enable Google Play services. // // Show dialog to allow users to install, update, or otherwise enable Google Play services.
GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), callingActivity, 0); // GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), callingActivity, 0);
} catch (GooglePlayServicesNotAvailableException e) { // } catch (GooglePlayServicesNotAvailableException e) {
Log.e("SecurityException", "Google Play Services not available."); // Log.e("SecurityException", "Google Play Services not available.");
} // }
} // }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
// only check when we actually need the permission
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED &&
sharedPref.getBoolean("pref_torrent_player", false)) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
} }
} }

View File

@ -15,7 +15,6 @@ import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
@ -93,8 +92,8 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
simpleExoPlayerView.setPlayer(player); simpleExoPlayerView.setPlayer(player);
// get video details from api // get video details from api
String apiBaseURL = APIUrlHelper.getUrl(this); String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL + "/api/v1/").create(GetVideoDataService.class); GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
Call<Video> call = service.getVideoData(videoID); Call<Video> call = service.getVideoData(videoID);

View File

@ -2,8 +2,6 @@ package net.schueller.peertube.adapter;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;

View File

@ -12,4 +12,8 @@ public class APIUrlHelper{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
return sharedPref.getString("pref_api_base", context.getResources().getString(R.string.pref_default_api_base_url)); return sharedPref.getString("pref_api_base", context.getResources().getString(R.string.pref_default_api_base_url));
} }
public static String getUrlWithVersion(Context context) {
return APIUrlHelper.getUrl(context) + "/api/v1/";
}
} }

View File

@ -0,0 +1,23 @@
package net.schueller.peertube.model;
public class OauthClient {
private String clientId;
private String clientSecret;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
}

View File

@ -0,0 +1,122 @@
package net.schueller.peertube.model;
public class Server {
private Integer id;
private String host;
private String name;
private String shortDescription;
private String version;
private Boolean signupAllowed;
private Integer userVideoQuota;
private Integer totalUsers;
private Integer totalVideos;
private Integer totalLocalVideos;
private Integer totalInstanceFollowers;
private Integer totalInstanceFollowing;
private Integer health;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getShortDescription() {
return shortDescription;
}
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public Boolean getSignupAllowed() {
return signupAllowed;
}
public void setSignupAllowed(Boolean signupAllowed) {
this.signupAllowed = signupAllowed;
}
public Integer getUserVideoQuota() {
return userVideoQuota;
}
public void setUserVideoQuota(Integer userVideoQuota) {
this.userVideoQuota = userVideoQuota;
}
public Integer getTotalUsers() {
return totalUsers;
}
public void setTotalUsers(Integer totalUsers) {
this.totalUsers = totalUsers;
}
public Integer getTotalVideos() {
return totalVideos;
}
public void setTotalVideos(Integer totalVideos) {
this.totalVideos = totalVideos;
}
public Integer getTotalLocalVideos() {
return totalLocalVideos;
}
public void setTotalLocalVideos(Integer totalLocalVideos) {
this.totalLocalVideos = totalLocalVideos;
}
public Integer getTotalInstanceFollowers() {
return totalInstanceFollowers;
}
public void setTotalInstanceFollowers(Integer totalInstanceFollowers) {
this.totalInstanceFollowers = totalInstanceFollowers;
}
public Integer getTotalInstanceFollowing() {
return totalInstanceFollowing;
}
public void setTotalInstanceFollowing(Integer totalInstanceFollowing) {
this.totalInstanceFollowing = totalInstanceFollowing;
}
public Integer getHealth() {
return health;
}
public void setHealth(Integer health) {
this.health = health;
}
}

View File

@ -0,0 +1,16 @@
package net.schueller.peertube.model;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
public class ServerList {
@SerializedName("data")
private ArrayList<Server> serverList;
public ArrayList<Server> getServerArrayList() {
return serverList;
}
}

View File

@ -0,0 +1,41 @@
package net.schueller.peertube.model;
public class Token {
private String accessToken;
private String expiresIn;
private String refreshToken;
private String tokenType;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(String expiresIn) {
this.expiresIn = expiresIn;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
}

View File

@ -0,0 +1,28 @@
package net.schueller.peertube.network;
import net.schueller.peertube.model.OauthClient;
import net.schueller.peertube.model.Token;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
public interface AuthenticationService {
@GET("oauth-clients/local")
Call<OauthClient> getOauthClientLocal();
@FormUrlEncoded
@POST("users/token")
Call<Token> getAuthenticationToken(
@Field("client_id") String clientId,
@Field("client_secret") String clientSecret,
@Field("response_type") String responseType,
@Field("grant_type") String grantType,
@Field("scope") String scope,
@Field("username") String username,
@Field("password") String password
);
}

View File

@ -0,0 +1,16 @@
package net.schueller.peertube.network;
import net.schueller.peertube.model.ServerList;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface GetServerListDataService {
@GET("instances/")
Call<ServerList> getInstancesData(
@Query("start") int start,
@Query("count") int count
);
}

View File

@ -30,21 +30,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:id="@+id/server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_server"
android:inputType="textUri"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -86,7 +71,6 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="@string/action_sign_in" android:text="@string/action_sign_in"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.SelectServerActivity">
</android.support.constraint.ConstraintLayout>

View File

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:keepScreenOn="true"
tools:context="net.schueller.peertube.activity.VideoPlayActivity"> tools:context="net.schueller.peertube.activity.VideoPlayActivity">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

View File

@ -0,0 +1,58 @@
<resources>
<string name="app_name">PeerTube</string>
<string name="title_activity_video_play">VideoPlayActivity</string>
<string name="title_activity_settings">Paramètres</string>
<string name="title_activity_login">Connexion</string>
<!-- Strings related to login -->
<string name="prompt_server">Serveur</string>
<string name="prompt_email">Email</string>
<string name="prompt_password">Mot de passe (optionnel)</string>
<string name="action_sign_in">Connexion</string>
<string name="action_sign_in_short">Connexion</string>
<string name="error_invalid_email">Cette adresse mail n\'est pas valide</string>
<string name="error_invalid_password">Ce mot de passe est trop court</string>
<string name="error_incorrect_password">Ce mot de passe est incorrect</string>
<string name="error_field_required">Ce champs est requis</string>
<string name="permission_rationale">"L\'autorisation sur les contacts est requise pour la complétion des adresses email."
</string>
<!-- Action bar -->
<string name="action_bar_title_search">Rechercher</string>
<string name="action_bar_title_settings">Paramètres</string>
<!-- Bottom navigation bar -->
<string name="bottom_nav_title_home">Accueil</string>
<string name="bottom_nav_title_trending">Tendances</string>
<string name="bottom_nav_title_subscriptions">Abonnements</string>
<string name="bottom_nav_title_account">Compte</string>
<!-- Strings related to Settings -->
<string name="peertube_required_server_version">1.0.0-alpha.7</string>
<string name="pref_default_api_base_url" formatted="false">https://troll.tv</string>
<string name="pref_title_peertube_server">PeerTube Server</string>
<!-- Strings related to Video meta data -->
<string name="meta_data_seperator">\u0020-\u0020</string>
<string name="meta_data_views">\u0020Views</string>
<string name="meta_data_owner_seperator">\@</string>
<string name="video_row_video_thumbnail">Miniature vidéo</string>
<string name="video_row_account_avatar">Avatar compte</string>
<string name="pref_title_show_nsfw">Afficher le contenu adulte</string>
<string name="pref_description_show_nsfw">Le contenu adulte sera affiché si activé</string>
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
<string name="pref_title_torrent_player">Lecteur Vidéo Torrent</string>
<string name="pref_description_torrent_player">Lecture de vidéo via un flux torrent</string>
<string name="pref_title_license">Licence</string>
<string name="pref_description_license">\n<b>GNU Affero General Public License v3.0</b>\n\nPermissions of this strongest copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available.</string>
<string name="pref_title_version">Version</string>
<string name="search_hint">Rechercher sur PeerTube</string>
<string name="title_activity_search">Rechercher</string>
</resources>

View File

@ -49,7 +49,7 @@
<string name="pref_description_show_nsfw">NSFW content will be shown if enabled.</string> <string name="pref_description_show_nsfw">NSFW content will be shown if enabled.</string>
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string> <string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
<string name="pref_title_torrent_player">Torrent Video Player</string> <string name="pref_title_torrent_player">Torrent Video Player</string>
<string name="pref_description_torrent_player">Videos playback via a torrent stream</string> <string name="pref_description_torrent_player">Video playback via a torrent stream. This requires Storage Permissions.</string>
<string name="pref_title_license">License</string> <string name="pref_title_license">License</string>
<string name="pref_description_license">\n<b>GNU Affero General Public License v3.0</b>\n\nPermissions of this strongest copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available.</string> <string name="pref_description_license">\n<b>GNU Affero General Public License v3.0</b>\n\nPermissions of this strongest copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available.</string>
<string name="pref_title_version">Version</string> <string name="pref_title_version">Version</string>