diff --git a/CHANGELOG.md b/CHANGELOG.md index 638ec4d..8de2769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +### Version 1.0.39 Tag: v1.0.39 (2020-06-27) + * exoplayer update (@lishoujun) + * Floating window support (@dhk2) + * Various translations + +### Version 1.0.38 Tag: v1.0.38 (2020-06-21) + * Multi server login address book + * Clear search history (@dhk2) + * Android SDK to 29 + * Various translations + +### Version 1.0.37 Tag: v1.0.37 (2020-06-19) + * Making Selecting a search suggestion fill search field (@dhk2) + * Adding configuration setting and supporting code to choose language (@dhk2) + * Adding configuration setting and code for configurable back button behavior (@dhk2) + * Various translations + ### Version 1.0.36 Tag: v1.0.36 (2020-06-14) * fix 'cannot make a new request because the previous response (@lishoujun) * Various translations @@ -17,6 +34,11 @@ * Gradle update * Translations +### Version 1.0.31 Tag: v1.0.31 (2019-09-25) +* Renamed overview to discover (PeerTube v1.4.0) +* Translations +* Gradle dependencies updates + ### Version 1.0.30 Tag: v1.0.30 (2019-08-07) * Gradle update * Translations diff --git a/app/build.gradle b/app/build.gradle index 3844e9c..6016cfa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,19 +1,27 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 + compileSdkVersion 29 defaultConfig { applicationId "net.schueller.peertube" minSdkVersion 21 - targetSdkVersion 28 - versionCode 1036 - versionName "1.0.36" + targetSdkVersion 29 + versionCode 1039 + versionName "1.0.39" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ext { libVersions = [ - exoplayer: '2.9.3' + exoplayer: '2.11.6' ] } + javaCompileOptions { + annotationProcessorOptions { + arguments = [ + "room.schemaLocation" : "$projectDir/schemas".toString(), + "room.incremental" : "true", + "room.expandProjection": "true"] + } + } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -70,7 +78,6 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - buildToolsVersion '28.0.3' applicationVariants.all { variant -> variant.resValue "string", "versionName", variant.versionName @@ -78,6 +85,23 @@ android { } dependencies { - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6' + def room_version = "2.2.5" + def archLifecycleVersion = '2.1.0' + + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7' implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android.material:material:1.1.0' + + // database lib + implementation "androidx.room:room-runtime:$room_version" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + annotationProcessor "androidx.room:room-compiler:$room_version" + androidTestImplementation "androidx.room:room-testing:$room_version" + + // Lifecycle components + implementation "androidx.lifecycle:lifecycle-extensions:$archLifecycleVersion" + annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$archLifecycleVersion" + } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5250d9f..32125e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,11 +2,7 @@ - - - - - + + + android:resource="@xml/searchable" /> - - + + + + + \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/activity/AccountActivity.java b/app/src/main/java/net/schueller/peertube/activity/AccountActivity.java index 4c4b5cf..d80b58c 100644 --- a/app/src/main/java/net/schueller/peertube/activity/AccountActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/AccountActivity.java @@ -48,6 +48,7 @@ import net.schueller.peertube.network.RetrofitInstance; import java.util.ArrayList; +import java.util.Objects; import java.util.Set; import androidx.annotation.NonNull; @@ -145,11 +146,10 @@ public class AccountActivity extends CommonActivity { Toolbar toolbar = findViewById(R.id.tool_bar_account); // Setting toolbar as the ActionBar with setSupportActionBar() call setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_baseline_close_24); getSupportActionBar().setTitle(displayNameAndHost); - getSupportActionBar().setHomeAsUpIndicator( - new IconicsDrawable(this, FontAwesome.Icon.faw_chevron_left).actionBar() - ); loadAccountVideos(displayNameAndHost); diff --git a/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java b/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java index b2d9461..36d5fe0 100644 --- a/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/AppCompatPreferenceActivity.java @@ -18,6 +18,7 @@ package net.schueller.peertube.activity; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Bundle; import android.preference.PreferenceActivity; @@ -25,10 +26,14 @@ import androidx.annotation.LayoutRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatDelegate; +import android.preference.PreferenceManager; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; +import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; +import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; + /** * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls * to be used with AppCompat. @@ -42,6 +47,20 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity { getDelegate().installViewFactory(); getDelegate().onCreate(savedInstanceState); super.onCreate(savedInstanceState); + + // TODO: cleanup this duplication + + // Set Night Mode + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + AppCompatDelegate.setDefaultNightMode(sharedPref.getBoolean("pref_dark_mode", false) ? + AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + + // Set theme + setTheme(getResources().getIdentifier( + sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), + "style", + getPackageName()) + ); } @Override diff --git a/app/src/main/java/net/schueller/peertube/activity/CommonActivity.java b/app/src/main/java/net/schueller/peertube/activity/CommonActivity.java index 60c3589..a733fca 100644 --- a/app/src/main/java/net/schueller/peertube/activity/CommonActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/CommonActivity.java @@ -19,12 +19,15 @@ package net.schueller.peertube.activity; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.os.Bundle; import android.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; +import java.util.Locale; + import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; @@ -45,6 +48,21 @@ public class CommonActivity extends AppCompatActivity { "style", getPackageName()) ); + + // Set language + String countryCode=sharedPref.getString("pref_language_app","en"); + Locale locale=new Locale(countryCode);; + //Neither Chinese language choice was working, found this fix on stack overflow + if(countryCode.equals("zh-rCN")) + locale = Locale.SIMPLIFIED_CHINESE; + if(countryCode.equals("zh-rTW")) + locale = Locale.TRADITIONAL_CHINESE; + + Locale.setDefault(locale); + Configuration config = getBaseContext().getResources().getConfiguration(); + config.locale = locale; + getBaseContext().getResources().updateConfiguration(config, + getBaseContext().getResources().getDisplayMetrics()); } } diff --git a/app/src/main/java/net/schueller/peertube/activity/MeActivity.java b/app/src/main/java/net/schueller/peertube/activity/MeActivity.java index 4153d74..572bb68 100644 --- a/app/src/main/java/net/schueller/peertube/activity/MeActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/MeActivity.java @@ -19,18 +19,20 @@ package net.schueller.peertube.activity; import android.content.Intent; +import android.net.Uri; +import android.nfc.Tag; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; -import com.mikepenz.fontawesome_typeface_library.FontAwesome; -import com.mikepenz.iconics.IconicsDrawable; - import net.schueller.peertube.R; import net.schueller.peertube.helper.APIUrlHelper; +import net.schueller.peertube.model.Avatar; import net.schueller.peertube.model.Me; import net.schueller.peertube.network.GetUserService; import net.schueller.peertube.network.RetrofitInstance; @@ -38,10 +40,17 @@ import net.schueller.peertube.network.Session; import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; + +import com.squareup.picasso.Picasso; + +import java.util.Objects; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import static net.schueller.peertube.application.AppApplication.getContext; + public class MeActivity extends CommonActivity { @@ -52,34 +61,10 @@ public class MeActivity extends CommonActivity { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_top_account, menu); - // Set an icon in the ActionBar - menu.findItem(R.id.action_logout).setIcon( - new IconicsDrawable(this, FontAwesome.Icon.faw_sign_out_alt).actionBar()); - return true; } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - // action with ID action_refresh was selected - - case R.id.action_logout: - Session.getInstance().invalidate(); - Intent intent = new Intent(this, LoginActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - this.startActivity(intent); - finish(); - return true; - default: - break; - } - - return super.onOptionsItemSelected(item); - } - @Override public boolean onSupportNavigateUp() { finish(); // close this activity as oppose to navigating up @@ -97,66 +82,99 @@ public class MeActivity extends CommonActivity { Toolbar toolbar = findViewById(R.id.tool_bar_me); // Setting toolbar as the ActionBar with setSupportActionBar() call setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeAsUpIndicator( - new IconicsDrawable(this, FontAwesome.Icon.faw_chevron_left).actionBar() - ); + Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_baseline_close_24); + + LinearLayout account = findViewById(R.id.a_me_account_line); + LinearLayout settings = findViewById(R.id.a_me_settings); + LinearLayout help = findViewById(R.id.a_me_helpnfeedback); + + TextView logout = findViewById(R.id.a_me_logout); - init(); - } + settings.setOnClickListener(view -> { + Intent settingsActivity = new Intent(getContext(), SettingsActivity.class); + //overridePendingTransition(R.anim.slide_in_bottom, 0); + startActivity(settingsActivity); + }); + + help.setOnClickListener(view -> { + String url = "https://github.com/sschueller/peertube-android/issues"; + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + startActivity(i); + }); + + logout.setOnClickListener(view -> { + Session.getInstance().invalidate(); + account.setVisibility(View.GONE); + + }); - private void init() { - // try to get user data getUserData(); } - private boolean getUserData() { - - // TODO + private void getUserData() { String apiBaseURL = APIUrlHelper.getUrlWithVersion(this); + String baseURL = APIUrlHelper.getUrl(this); GetUserService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetUserService.class); Call call = service.getMe(); call.enqueue(new Callback() { + + LinearLayout account = findViewById(R.id.a_me_account_line); + @Override public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { Me me = response.body(); - TextView username = findViewById(R.id.account_username); - TextView email = findViewById(R.id.account_email); + Log.d(TAG, response.body().toString()); + + TextView username = findViewById(R.id.a_me_username); + TextView email = findViewById(R.id.a_me_email); + ImageView avatarView = findViewById(R.id.a_me_avatar); + username.setText(me.getUsername()); email.setText(me.getEmail()); - Log.v(TAG, me.getEmail()); + Avatar avatar = me.getAccount().getAvatar(); + if (avatar != null) { + String avatarPath = avatar.getPath(); + Picasso.get() + .load(baseURL + avatarPath) + .into(avatarView); + } + account.setVisibility(View.VISIBLE); + + } else { + account.setVisibility(View.GONE); } - } @Override - public void onFailure(Call call, Throwable t) { - + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + account.setVisibility(View.GONE); } }); - return true; } @Override protected void onResume() { super.onResume(); - init(); + getUserData(); } } diff --git a/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java b/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java index b2da65f..26d56f2 100644 --- a/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/SelectServerActivity.java @@ -1,3 +1,20 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package net.schueller.peertube.activity; import androidx.annotation.NonNull; @@ -9,6 +26,7 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; @@ -54,28 +72,6 @@ public class SelectServerActivity extends AppCompatActivity { loadList(); - // set url - TextView selectedUrl = findViewById(R.id.serverSelectedUrl); - selectedUrl.setText(APIUrlHelper.getUrl(SelectServerActivity.this)); - - Button setServerButton = findViewById(R.id.server_selection_set); - setServerButton.setOnClickListener(v -> { - - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); - SharedPreferences.Editor editor = sharedPref.edit(); - - String serverUrl = APIUrlHelper.cleanServerUrl(selectedUrl.getText().toString()); - - if (!Patterns.WEB_URL.matcher(serverUrl).matches()) { - Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_LONG).show(); - } else { - editor.putString("pref_api_base", serverUrl); - editor.apply(); - this.finish(); - } - - }); - } diff --git a/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.java b/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.java new file mode 100644 index 0000000..8d2f934 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.java @@ -0,0 +1,173 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.activity; + +import android.app.AlertDialog; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.EditText; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import net.schueller.peertube.R; +import net.schueller.peertube.adapter.ServerListAdapter; +import net.schueller.peertube.database.Server; +import net.schueller.peertube.database.ServerViewModel; +import net.schueller.peertube.fragment.AddServerFragment; + + +public class ServerAddressBookActivity extends CommonActivity implements AddServerFragment.OnFragmentInteractionListener { + + private String TAG = "ServerAddressBookActivity"; + public static final String EXTRA_REPLY = "net.schueller.peertube.room.REPLY"; + + private ServerViewModel mServerViewModel; + private AddServerFragment addServerFragment; + private FloatingActionButton floatingActionButton; + private FragmentManager fragmentManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_server_address_book); + + + mServerViewModel = new ViewModelProvider(this).get(ServerViewModel.class); + + showServers(); + + floatingActionButton = findViewById(R.id.add_server); + floatingActionButton.setOnClickListener(view -> { + + Log.d(TAG, "Click"); + + fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + + addServerFragment = new AddServerFragment(); + fragmentTransaction.replace(R.id.server_book, addServerFragment); + fragmentTransaction.commit(); + + floatingActionButton.hide(); + + }); + + } + + @Override + public void onFragmentInteraction(Uri uri) { + + } + + @Override + public void onPointerCaptureChanged(boolean hasCapture) { + + } + + + public void showServers() + { + RecyclerView recyclerView = findViewById(R.id.server_list_recyclerview); + final ServerListAdapter adapter = new ServerListAdapter(this); + recyclerView.setAdapter(adapter); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + // Delete items on swipe + ItemTouchHelper helper = new ItemTouchHelper( + new ItemTouchHelper.SimpleCallback(0, + ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, + int direction) { + + + new AlertDialog.Builder(ServerAddressBookActivity.this) + .setTitle(getString(R.string.server_book_del_alert_title)) + .setMessage(getString(R.string.server_book_del_alert_msg)) + .setPositiveButton(android.R.string.yes, (dialog, which) -> { + int position = viewHolder.getAdapterPosition(); + Server server = adapter.getServerAtPosition(position); +// Toast.makeText(ServerAddressBookActivity.this, "Deleting " + +// server.getServerName(), Toast.LENGTH_LONG).show(); + // Delete the server + mServerViewModel.delete(server); + }) + .setNegativeButton(android.R.string.no, (dialog, which) -> { + adapter.notifyItemChanged(viewHolder.getAdapterPosition()); + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + + } + }); + helper.attachToRecyclerView(recyclerView); + + + // Update the cached copy of the words in the adapter. + mServerViewModel.getAllServers().observe(this, adapter::setServers); + + } + + public void addServer(View view) + { + Log.d(TAG, "addServer"); + + EditText serverLabel = view.findViewById(R.id.serverLabel); + EditText serverUrl = view.findViewById(R.id.serverUrl); + EditText serverUsername = view.findViewById(R.id.serverUsername); + EditText serverPassword = view.findViewById(R.id.serverPassword); + + Server server = new Server(serverLabel.getText().toString()); + + server.setServerHost(serverUrl.getText().toString()); + server.setUsername(serverUsername.getText().toString()); + server.setPassword(serverPassword.getText().toString()); + + mServerViewModel.insert(server); + + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.remove(addServerFragment); + fragmentTransaction.commit(); + + floatingActionButton.show(); + + } + + public void testServer() + { + + } + +} diff --git a/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java index 2179de4..c8be62b 100644 --- a/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java @@ -135,13 +135,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { - // Set theme - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); - setTheme(getResources().getIdentifier( - sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), - "style", - getPackageName()) - ); super.onCreate(savedInstanceState); @@ -202,7 +195,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { // to their values. When their values change, their summaries are // updated to reflect the new value, per the Android Design // guidelines. - bindPreferenceSummaryToValue(findPreference("pref_api_base")); + //bindPreferenceSummaryToValue(findPreference("pref_api_base")); bindPreferenceSummaryToValue(findPreference("pref_theme")); } @@ -219,12 +212,12 @@ public class SettingsActivity extends AppCompatPreferenceActivity { @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - String key = preference.getKey(); - if ("pref_api_base".equals(key)) { - Intent intentServer = new Intent(preference.getContext(), SelectServerActivity.class); - startActivity(intentServer); - return true; - } +// String key = preference.getKey(); +// if ("pref_api_base".equals(key)) { +// Intent intentServer = new Intent(preference.getContext(), SelectServerActivity.class); +// startActivity(intentServer); +// return true; +// } return false; } @@ -232,7 +225,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { public void onResume() { setPreferenceScreen(null); addPreferencesFromResource(R.xml.pref_general); - bindPreferenceSummaryToValue(findPreference("pref_api_base")); + //bindPreferenceSummaryToValue(findPreference("pref_api_base")); super.onResume(); } diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java index 8a68ce6..c4ca315 100644 --- a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java @@ -19,11 +19,14 @@ package net.schueller.peertube.activity; import android.Manifest; +import android.app.AlertDialog; import android.app.SearchManager; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.database.Cursor; import android.preference.PreferenceManager; import android.provider.SearchRecentSuggestions; @@ -47,6 +50,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; @@ -78,6 +82,7 @@ public class VideoListActivity extends CommonActivity { public static final String EXTRA_VIDEOID = "VIDEOID"; public static final String EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST"; + public static final Integer SWITCH_INSTANCE = 2; private VideoAdapter videoAdapter; private SwipeRefreshLayout swipeRefreshLayout; @@ -122,14 +127,11 @@ public class VideoListActivity extends CommonActivity { inflater.inflate(R.menu.menu_top_videolist, menu); // Set an icon in the ActionBar - menu.findItem(R.id.action_settings).setIcon( - new IconicsDrawable(this, FontAwesome.Icon.faw_cog).actionBar()); - menu.findItem(R.id.action_account).setIcon( new IconicsDrawable(this, FontAwesome.Icon.faw_user_circle).actionBar()); -// menu.findItem(R.id.action_server_selection).setIcon( -// new IconicsDrawable(this, FontAwesome.Icon.faw_server).actionBar()); + menu.findItem(R.id.action_server_address_book).setIcon( + new IconicsDrawable(this, FontAwesome.Icon.faw_server).actionBar()); MenuItem searchMenuItem = menu.findItem(R.id.action_search); @@ -145,6 +147,26 @@ public class VideoListActivity extends CommonActivity { searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default searchView.setQueryRefinementEnabled(true); + searchMenuItem.getActionView().setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + new AlertDialog.Builder(VideoListActivity.this) + .setTitle(getString(R.string.clear_search_history)) + .setMessage(getString(R.string.clear_search_history_prompt)) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + SearchRecentSuggestions suggestions = new SearchRecentSuggestions(getApplicationContext(), + SearchSuggestionsProvider.AUTHORITY, + SearchSuggestionsProvider.MODE); + suggestions.clearHistory(); + } + }) + .setNegativeButton(android.R.string.no, null) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + return true; + } + }); searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem menuItem) { @@ -166,7 +188,27 @@ public class VideoListActivity extends CommonActivity { Log.d(TAG, "onDismiss: "); loadVideos(0, count, sort, filter); }); + searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { + @Override + public boolean onSuggestionClick(int position) { + String suggestion = getSuggestion(position); + searchView.setQuery(suggestion, true); + return true; + } + private String getSuggestion(int position) { + Cursor cursor = (Cursor) searchView.getSuggestionsAdapter().getItem( + position); + return cursor.getString(cursor + .getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)); + } + + @Override + public boolean onSuggestionSelect(int position) { + /* Required to implement */ + return true; + } + }); return true; } @@ -176,6 +218,16 @@ public class VideoListActivity extends CommonActivity { stopService(new Intent(this, VideoPlayerService.class)); } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == SWITCH_INSTANCE) { + if(resultCode == RESULT_OK) { + loadVideos(currentStart, count, sort, filter); + } + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will @@ -188,25 +240,28 @@ public class VideoListActivity extends CommonActivity { //Toast.makeText(this, "Search Selected", Toast.LENGTH_SHORT).show(); return false; - case R.id.action_settings: -// Toast.makeText(this, "Login Selected", Toast.LENGTH_SHORT).show(); - Intent intentSettings = new Intent(this, SettingsActivity.class); - this.startActivity(intentSettings); - - return true; case R.id.action_account: - if (!Session.getInstance().isLoggedIn()) { - Intent intentLogin = new Intent(this, LoginActivity.class); - this.startActivity(intentLogin); - } else { +// if (!Session.getInstance().isLoggedIn()) { + + //Intent intentLogin = new Intent(this, ServerAddressBookActivity.class); + Intent intentMe = new Intent(this, MeActivity.class); this.startActivity(intentMe); - } + + //overridePendingTransition(R.anim.slide_in_bottom, 0); + + + // this.startActivity(intentLogin); + +// } else { +// Intent intentMe = new Intent(this, MeActivity.class); +// this.startActivity(intentMe); +// } + return false; + case R.id.action_server_address_book: + Intent addressBookActivityIntent = new Intent(this, ServerAddressBookActivity.class); + this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE); return false; -// case R.id.action_server_selection: -// Intent intentServer = new Intent(this, SelectServerActivity.class); -// this.startActivity(intentServer); -// return false; default: break; } @@ -335,6 +390,7 @@ public class VideoListActivity extends CommonActivity { @Override protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); setIntent(intent); handleIntent(intent); } @@ -440,8 +496,10 @@ public class VideoListActivity extends CommonActivity { //Log.v(TAG, "navigation_subscriptions"); if (!Session.getInstance().isLoggedIn()) { - Intent intent = new Intent(this, LoginActivity.class); - this.startActivity(intent); +// Intent intent = new Intent(this, LoginActivity.class); +// this.startActivity(intent); + Intent addressBookActivityIntent = new Intent(this, ServerAddressBookActivity.class); + this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE); return false; } else { diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java index 2b69783..28e80ad 100644 --- a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java @@ -19,16 +19,24 @@ package net.schueller.peertube.activity; +import android.annotation.SuppressLint; +import android.app.AppOpsManager; +import android.app.PictureInPictureParams; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; +import android.text.TextUtils; import android.util.Log; +import android.util.Rational; import android.util.TypedValue; import android.view.WindowManager; @@ -39,6 +47,7 @@ import android.widget.RelativeLayout; import net.schueller.peertube.R; import net.schueller.peertube.fragment.VideoMetaDataFragment; import net.schueller.peertube.fragment.VideoPlayerFragment; +import net.schueller.peertube.service.VideoPlayerService; import java.util.Objects; @@ -71,13 +80,23 @@ public class VideoPlayActivity extends AppCompatActivity { // get video ID Intent intent = getIntent(); String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID); - Log.v(TAG, "click: " + videoUuid); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); assert videoPlayerFragment != null; - videoPlayerFragment.start(videoUuid); + String playingVideo = videoPlayerFragment.getVideoUuid(); + Log.v(TAG, "oncreate click: " + videoUuid +" is trying to replace: "+playingVideo); + + if (TextUtils.isEmpty(playingVideo)){ + Log.v(TAG,"oncreate no video currently playing"); + videoPlayerFragment.start(videoUuid); + } else if(!playingVideo.equals(videoUuid)){ + Log.v(TAG,"oncreate different video playing currently"); + videoPlayerFragment.stopVideo(); + videoPlayerFragment.start(videoUuid); + } else { + Log.v(TAG,"oncreate same video playing currently"); + } // if we are in landscape set the video to fullscreen int orientation = this.getResources().getConfiguration().orientation; @@ -86,6 +105,36 @@ public class VideoPlayActivity extends AppCompatActivity { } } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) + getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); + assert videoPlayerFragment != null; + String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID); + Log.v(TAG, "new intent click: " + videoUuid +" is trying to replace: "+videoPlayerFragment.getVideoUuid()); + assert videoPlayerFragment != null; + String playingVideo = videoPlayerFragment.getVideoUuid(); + + if (TextUtils.isEmpty(playingVideo)){ + Log.v(TAG,"new intent no video currently playing"); + videoPlayerFragment.start(videoUuid); + } else if(!playingVideo.equals(videoUuid)){ + Log.v(TAG,"new intent different video playing currently"); + videoPlayerFragment.stopVideo(); + videoPlayerFragment.start(videoUuid); + } else { + Log.v(TAG,"new intent same video playing currently"); + } + + // if we are in landscape set the video to fullscreen + int orientation = this.getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + setOrientation(true); + } + + } @Override public void onConfigurationChanged(Configuration newConfig) { @@ -199,4 +248,115 @@ public class VideoPlayActivity extends AppCompatActivity { Log.v(TAG, "onStart()..."); } + @SuppressLint("NewApi") + @Override + public void onUserLeaveHint () { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + FragmentManager fragmentManager = getSupportFragmentManager(); + VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); + VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment); + String backgroundBehavior = sharedPref.getString("pref_background_behavior","backgroundStop"); + + switch(backgroundBehavior){ + case "backgroundStop": + Log.v(TAG,"stop the video"); + videoPlayerFragment.pauseVideo(); + stopService(new Intent(this, VideoPlayerService.class)); + super.onBackPressed(); + break; + case "backgroundAudio": + Log.v(TAG,"play the Audio"); + super.onBackPressed(); + break; + case "backgroundFloat": + Log.v(TAG,"play in floating video"); + //canEnterPIPMode makes sure API level is high enough + if (canEnterPipMode(this)) { + Log.v(TAG, "enabling pip"); + enterPipMode(); + } else { + Log.v(TAG, "unable to use pip"); + } + break; + } + Log.v(TAG, "onUserLeaveHint()..."); + } + + // @RequiresApi(api = Build.VERSION_CODES.O) + @SuppressLint("NewApi") + public void onBackPressed() { + + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) + getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); + + //copying Youtube behavior to have back button exit full screen. + if (videoPlayerFragment.getIsFullscreen()){ + Log.v(TAG,"exiting full screen"); + videoPlayerFragment.fullScreenToggle(); + return; + } + + if (sharedPref.getBoolean("pref_back_pause", true)) { + assert videoPlayerFragment != null; + videoPlayerFragment.pauseVideo(); + } + + String backgroundBehavior = sharedPref.getString("pref_background_behavior","backgroundStop"); + switch (backgroundBehavior){ + case "backgroundStop": + Log.v(TAG,"stop the video"); + videoPlayerFragment.pauseVideo(); + stopService(new Intent(this, VideoPlayerService.class)); + super.onBackPressed(); + break; + case "backgroundAudio": + Log.v(TAG,"play the Audio"); + super.onBackPressed(); + break; + case "backgroundFloat": + Log.v(TAG,"play in floating video"); + //canEnterPIPMode makes sure API level is high enough + if (canEnterPipMode(this)) { + Log.v(TAG, "enabling pip"); + enterPipMode(); + //fixes problem where back press doesn't bring up video list after returning from PIP mode + Intent intentSettings = new Intent(this, VideoListActivity.class); + this.startActivity(intentSettings); + } else { + Log.v(TAG,"Unable to enter PIP mode"); + super.onBackPressed(); + } + break; + } + Log.v(TAG, "onBackPressed()..."); + } + public boolean canEnterPipMode(Context context) { + Log.v(TAG,"api version "+Build.VERSION.SDK_INT); + if (Build.VERSION.SDK_INT<28){ + return false; + } + AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + return (AppOpsManager.MODE_ALLOWED== appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName())); + } + @RequiresApi(api = Build.VERSION_CODES.O) + public void enterPipMode() { + Rational rational = new Rational(239, 100); + Log.v(TAG,rational.toString()); + PictureInPictureParams mParams = + new PictureInPictureParams.Builder() + .setAspectRatio(rational) +// .setSourceRectHint(new Rect(0,500,400,600)) + .build(); + + enterPictureInPictureMode(mParams); + } + @Override + public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) { + if (isInPictureInPictureMode) { + Log.v(TAG,"switched to pip "); + } else { + Log.v(TAG,"switched to normal"); + } + } } diff --git a/app/src/main/java/net/schueller/peertube/adapter/ServerAdapter.java b/app/src/main/java/net/schueller/peertube/adapter/ServerAdapter.java index 0e3de08..7bde807 100644 --- a/app/src/main/java/net/schueller/peertube/adapter/ServerAdapter.java +++ b/app/src/main/java/net/schueller/peertube/adapter/ServerAdapter.java @@ -18,6 +18,7 @@ package net.schueller.peertube.adapter; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.view.LayoutInflater; @@ -37,6 +38,8 @@ import java.util.ArrayList; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import static android.app.Activity.RESULT_OK; + public class ServerAdapter extends RecyclerView.Adapter { @@ -75,18 +78,25 @@ public class ServerAdapter extends RecyclerView.Adapter { - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity); - SharedPreferences.Editor editor = sharedPref.edit(); +// SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity); +// SharedPreferences.Editor editor = sharedPref.edit(); String serverUrl = APIUrlHelper.cleanServerUrl(serverList.get(position).getHost()); - editor.putString("pref_api_base", serverUrl); - editor.apply(); - - activity.finish(); +// editor.putString("pref_api_base", serverUrl); +// editor.apply(); +// +// Toast.makeText(activity, activity.getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show(); + Intent intent = new Intent(); + intent.putExtra("serverUrl", serverUrl); + intent.putExtra("serverName", serverList.get(position).getName()); + activity.setResult(RESULT_OK, intent); + + activity.finish(); + }); // diff --git a/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.java b/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.java new file mode 100644 index 0000000..f7225df --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.java @@ -0,0 +1,158 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.adapter; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.provider.SearchRecentSuggestions; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import net.schueller.peertube.R; + +import net.schueller.peertube.activity.SelectServerActivity; +import net.schueller.peertube.activity.ServerAddressBookActivity; +import net.schueller.peertube.activity.VideoListActivity; +import net.schueller.peertube.database.Server; +import net.schueller.peertube.helper.APIUrlHelper; +import net.schueller.peertube.provider.SearchSuggestionsProvider; +import net.schueller.peertube.service.LoginService; + + +import java.util.List; + +import static android.app.Activity.RESULT_OK; + +public class ServerListAdapter extends RecyclerView.Adapter { + + + private final LayoutInflater mInflater; + private List mServers; // Cached copy of Servers + + public ServerListAdapter(Context context) { + this.mInflater = LayoutInflater.from(context); + } + + @NonNull + @Override + public ServerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View itemView = mInflater.inflate(R.layout.row_serverbook, parent, false); + return new ServerViewHolder(itemView); + } + + @Override + public void onBindViewHolder(@NonNull ServerViewHolder holder, int position) { + + if (mServers != null) { + Server current = mServers.get(position); + holder.serverLabel.setText(current.getServerName()); + holder.serverUrl.setText(current.getServerHost()); + + if (TextUtils.isEmpty(current.getUsername())) { + holder.hasLogin.setVisibility(View.GONE); + } else { + holder.hasLogin.setVisibility(View.VISIBLE); + } + + } else { + // Covers the case of data not being ready yet. + holder.serverLabel.setText(R.string.server_book_no_servers_found); + } + + holder.itemView.setOnClickListener(v -> { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mInflater.getContext()); + SharedPreferences.Editor editor = sharedPref.edit(); + + String serverUrl = APIUrlHelper.cleanServerUrl(getServerAtPosition(position).getServerHost()); + + editor.putString("pref_api_base", serverUrl); + editor.apply(); + + // attempt authentication if we have a username + if (!TextUtils.isEmpty(getServerAtPosition(position).getUsername())) { + LoginService.Authenticate( + getServerAtPosition(position).getUsername(), + getServerAtPosition(position).getPassword() + ); + } + + // tell server list activity to reload list + Intent intent = new Intent(); + ((Activity) mInflater.getContext()).setResult(RESULT_OK, intent); + + // close this activity + ((Activity) mInflater.getContext()).finish(); + + Toast.makeText(mInflater.getContext(), mInflater.getContext().getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show(); + + }); + + +// +// holder.itemView.setOnLongClickListener(v -> { +// Log.v("ServerListAdapter", "setOnLongClickListener " + position); +// return true; +// }); + + + } + + public void setServers(List Servers) { + mServers = Servers; + this.notifyDataSetChanged(); + } + + // getItemCount() is called many times, and when it is first called, + // mServers has not been updated (means initially, it's null, and we can't return null). + @Override + public int getItemCount() { + if (mServers != null) + return mServers.size(); + else return 0; + } + + static class ServerViewHolder extends RecyclerView.ViewHolder { + TextView serverLabel, serverUrl, serverUsername; + ImageView hasLogin; + + private ServerViewHolder(View itemView) { + super(itemView); + serverLabel = itemView.findViewById(R.id.serverLabelRow); + serverUrl = itemView.findViewById(R.id.serverUrlRow); + hasLogin = itemView.findViewById(R.id.sb_row_has_login_icon); + } + } + + public Server getServerAtPosition (int position) { + return mServers.get(position); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/application/AppApplication.java b/app/src/main/java/net/schueller/peertube/application/AppApplication.java index 05869b4..98f0996 100644 --- a/app/src/main/java/net/schueller/peertube/application/AppApplication.java +++ b/app/src/main/java/net/schueller/peertube/application/AppApplication.java @@ -1,3 +1,20 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package net.schueller.peertube.application; import android.app.Application; diff --git a/app/src/main/java/net/schueller/peertube/database/AppDatabase.java b/app/src/main/java/net/schueller/peertube/database/AppDatabase.java new file mode 100644 index 0000000..bc1643e --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/AppDatabase.java @@ -0,0 +1,26 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database; + +import androidx.room.Database; +import androidx.room.RoomDatabase; + +@Database(entities = {Server.class}, version = 1) +public abstract class AppDatabase extends RoomDatabase { + public abstract ServerDao serverDao(); +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/Server.java b/app/src/main/java/net/schueller/peertube/database/Server.java new file mode 100644 index 0000000..5c0f7e1 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/Server.java @@ -0,0 +1,87 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database; + +import androidx.annotation.NonNull; +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "server_table") +public class Server { + + @PrimaryKey(autoGenerate = true) + private int id; + + @NonNull + @ColumnInfo(name = "server_name") + private String serverName; + + @ColumnInfo(name = "server_host") + private String serverHost; + + @ColumnInfo(name = "username") + private String username; + + @ColumnInfo(name = "password") + private String password; + + public Server(@NonNull String serverName) { + this.serverName = serverName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public String getServerHost() { + return serverHost; + } + + public void setServerHost(String serverHost) { + this.serverHost = serverHost; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/app/src/main/java/net/schueller/peertube/database/ServerDao.java b/app/src/main/java/net/schueller/peertube/database/ServerDao.java new file mode 100644 index 0000000..e01ef5f --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/ServerDao.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import java.util.List; + +@Dao +public interface ServerDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(Server server); + + @Query("DELETE FROM server_table") + void deleteAll(); + + @Delete + void delete(Server server); + + @Query("SELECT * from server_table ORDER BY server_name DESC") + LiveData> getAllServers(); +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/ServerRepository.java b/app/src/main/java/net/schueller/peertube/database/ServerRepository.java new file mode 100644 index 0000000..001cb7c --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/ServerRepository.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database; + +import android.app.Application; +import android.os.AsyncTask; + + +import androidx.lifecycle.LiveData; + +import java.util.List; + +class ServerRepository { + + private ServerDao mServerDao; + private LiveData> mAllServers; + + ServerRepository(Application application) { + ServerRoomDatabase db = ServerRoomDatabase.getDatabase(application); + mServerDao = db.serverDao(); + mAllServers = mServerDao.getAllServers(); + + } + + LiveData> getAllServers() { + return mAllServers; + } + + void insert (Server server) { + new insertAsyncTask(mServerDao).execute(server); + } + + public void delete(Server server) { + new deleteServerAsyncTask(mServerDao).execute(server); + } + + private static class insertAsyncTask extends AsyncTask { + + private ServerDao mAsyncTaskDao; + + insertAsyncTask(ServerDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(final Server... params) { + mAsyncTaskDao.insert(params[0]); + return null; + } + } + + private static class deleteServerAsyncTask extends AsyncTask { + private ServerDao mAsyncTaskDao; + + deleteServerAsyncTask(ServerDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(final Server... params) { + mAsyncTaskDao.delete(params[0]); + return null; + } + } +} diff --git a/app/src/main/java/net/schueller/peertube/database/ServerRoomDatabase.java b/app/src/main/java/net/schueller/peertube/database/ServerRoomDatabase.java new file mode 100644 index 0000000..deb79a2 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/ServerRoomDatabase.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database; + +import android.content.Context; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Database(entities = {Server.class}, version = 1, exportSchema = false) +public abstract class ServerRoomDatabase extends RoomDatabase { + + public abstract ServerDao serverDao(); + + private static volatile ServerRoomDatabase INSTANCE; + + private static final int NUMBER_OF_THREADS = 4; + + static final ExecutorService databaseWriteExecutor = + Executors.newFixedThreadPool(NUMBER_OF_THREADS); + + public static ServerRoomDatabase getDatabase(final Context context) { + if (INSTANCE == null) { + synchronized (ServerRoomDatabase.class) { + if (INSTANCE == null) { + if (INSTANCE == null) { + INSTANCE = Room.databaseBuilder(context.getApplicationContext(), + ServerRoomDatabase.class, "server_database") + .build(); + } + } + } + } + return INSTANCE; + } + +} + diff --git a/app/src/main/java/net/schueller/peertube/database/ServerViewModel.java b/app/src/main/java/net/schueller/peertube/database/ServerViewModel.java new file mode 100644 index 0000000..bdd9b1c --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/ServerViewModel.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database; + +import android.app.Application; + +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; + +import java.util.List; + +public class ServerViewModel extends AndroidViewModel { + + private ServerRepository mRepository; + + private LiveData> mAllServers; + + public ServerViewModel (Application application) { + super(application); + mRepository = new ServerRepository(application); + mAllServers = mRepository.getAllServers(); + } + + public LiveData> getAllServers() { return mAllServers; } + + public void insert(Server server) { mRepository.insert(server); } + + public void delete(Server server) {mRepository.delete(server);} + +} diff --git a/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.java b/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.java new file mode 100644 index 0000000..6e15a2b --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.java @@ -0,0 +1,178 @@ +/* + * Copyright 2018 Stefan Schüller + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.fragment; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import android.text.TextUtils; +import android.util.Log; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import net.schueller.peertube.R; +import net.schueller.peertube.activity.SelectServerActivity; +import net.schueller.peertube.activity.ServerAddressBookActivity; +import net.schueller.peertube.helper.APIUrlHelper; + +import static android.app.Activity.RESULT_OK; + + +public class AddServerFragment extends Fragment { + + public static final String TAG = "AddServerFragment"; + public static final Integer PICK_SERVER = 1; + + private OnFragmentInteractionListener mListener; + + private View mView; + + public AddServerFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + Log.d(TAG, "onCreateView"); + // Inflate the layout for this fragment + + mView = inflater.inflate(R.layout.fragment_add_server, container, false); + + // bind button click + Button addServerButton = mView.findViewById(R.id.addServerButton); + addServerButton.setOnClickListener(view -> { + + Activity act = getActivity(); + + Boolean formValid = true; + + // close keyboard + try { + InputMethodManager inputManager = (InputMethodManager) + act.getSystemService(Context.INPUT_METHOD_SERVICE); + + inputManager.hideSoftInputFromWindow(act.getCurrentFocus().getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } catch (Exception e) { + + } + + EditText selectedLabel = mView.findViewById(R.id.serverLabel); + if ( TextUtils.isEmpty(selectedLabel.getText())){ + selectedLabel.setError( act.getString(R.string.server_book_label_is_required )); + Toast.makeText(act, R.string.invalid_url, Toast.LENGTH_LONG).show(); + formValid = false; + } + + // validate url + EditText selectedUrl = mView.findViewById(R.id.serverUrl); + String serverUrl = APIUrlHelper.cleanServerUrl(selectedUrl.getText().toString()); + selectedUrl.setText(serverUrl); + + if (!Patterns.WEB_URL.matcher(serverUrl).matches()) { + selectedUrl.setError( act.getString(R.string.server_book_valid_url_is_required ) ); + Toast.makeText(act, R.string.invalid_url, Toast.LENGTH_LONG).show(); + formValid = false; + } + + if (formValid) { + if (act instanceof ServerAddressBookActivity) { + ((ServerAddressBookActivity) act).addServer(mView); + + } + } + + }); + +// Button testServerButton = mView.findViewById(R.id.testServerButton); +// testServerButton.setOnClickListener(view -> { +// Activity act = getActivity(); +// if (act instanceof ServerAddressBookActivity) { +// ((ServerAddressBookActivity) act).testServer(); +// } +// }); + + Button pickServerUrl = mView.findViewById(R.id.pickServerUrl); + pickServerUrl.setOnClickListener(view -> { + Intent intentServer = new Intent(getActivity(), SelectServerActivity.class); + this.startActivityForResult(intentServer, PICK_SERVER); + }); + + return mView; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_SERVER) { + if(resultCode == RESULT_OK) { + + String serverUrlTest = data.getStringExtra("serverUrl"); + //Log.d(TAG, "serverUrl " + serverUrlTest); + EditText serverUrl = mView.findViewById(R.id.serverUrl); + serverUrl.setText(serverUrlTest); + + EditText serverLabel = mView.findViewById(R.id.serverLabel); + if ("".equals(serverLabel.getText().toString())) { + serverLabel.setText(data.getStringExtra("serverName")); + } + + } + } + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (context instanceof OnFragmentInteractionListener) { + mListener = (OnFragmentInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + public interface OnFragmentInteractionListener { + // TODO: Update argument type and name + void onFragmentInteraction(Uri uri); + } +} diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoPlayerFragment.java b/app/src/main/java/net/schueller/peertube/fragment/VideoPlayerFragment.java index 62e8d24..a37af1c 100644 --- a/app/src/main/java/net/schueller/peertube/fragment/VideoPlayerFragment.java +++ b/app/src/main/java/net/schueller/peertube/fragment/VideoPlayerFragment.java @@ -18,6 +18,7 @@ package net.schueller.peertube.fragment; import android.app.Activity; +import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -25,12 +26,15 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.preference.PreferenceManager; import android.util.Log; +import android.view.GestureDetector; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; @@ -65,6 +69,7 @@ import net.schueller.peertube.service.VideoPlayerService; import java.util.Objects; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.fragment.app.Fragment; import retrofit2.Call; import retrofit2.Callback; @@ -83,7 +88,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL private LinearLayout torrentStatus; private static final String TAG = "VideoPlayerFragment"; - + private GestureDetector mDetector; private ServiceConnection mConnection = new ServiceConnection() { @@ -135,6 +140,9 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL simpleExoPlayerView.setControllerShowTimeoutMs(1000); simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); + mDetector = new GestureDetector(context, new MyGestureListener()); + simpleExoPlayerView.setOnTouchListener(touchListener); + torrentStatus = activity.findViewById(R.id.exo_torrent_status); // Full screen Icon @@ -146,13 +154,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL fullscreenButton.setOnClickListener(view -> { Log.d(TAG, "Fullscreen"); - if (!isFullscreen) { - isFullscreen = true; - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } else { - isFullscreen = false; - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } + fullScreenToggle(); }); if (!mBound) { @@ -182,7 +184,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL mService.setCurrentVideo(video); if (video == null) { - Toast.makeText(context, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show(); + Toast.makeText(context, "Unable to retrieve video information, try again later.", Toast.LENGTH_SHORT).show(); return; } @@ -193,7 +195,7 @@ public class VideoPlayerFragment extends Fragment implements VideoRendererEventL @Override public void onFailure(@NonNull Call