commit
848106a857
@ -1,4 +1,9 @@
|
||||
apply plugin: 'com.android.application'
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-parcelize'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
ext.readProperty = { paramName -> readPropertyWithDefault(paramName, null) }
|
||||
ext.readPropertyWithDefault = { paramName, defaultValue ->
|
||||
@ -80,20 +85,29 @@ android {
|
||||
applicationVariants.all { variant ->
|
||||
variant.resValue "string", "versionName", variant.versionName
|
||||
}
|
||||
|
||||
buildFeatures{
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def room_version = "2.2.6"
|
||||
def lifecycleVersion = '2.2.0'
|
||||
def exoplayer = '2.12.3'
|
||||
def fragment_version = "1.2.5"
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
// Layouts and design
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
implementation 'de.hdodenhof:circleimageview:3.0.0'
|
||||
|
||||
@ -134,18 +148,28 @@ dependencies {
|
||||
|
||||
// database lib
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
androidTestImplementation "androidx.room:room-testing:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
// Lifecycle components
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
|
||||
annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||
kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
|
||||
// testing
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
androidTestImplementation "androidx.room:room-testing:$room_version"
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.activity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.adapter.ServerListAdapter;
|
||||
import net.schueller.peertube.database.Server;
|
||||
import net.schueller.peertube.database.ServerViewModel;
|
||||
import net.schueller.peertube.fragment.AddServerFragment;
|
||||
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ServerAddressBookActivity extends CommonActivity implements AddServerFragment.OnFragmentInteractionListener {
|
||||
|
||||
private String TAG = "ServerAddressBookActivity";
|
||||
public static final String EXTRA_REPLY = "net.schueller.peertube.room.REPLY";
|
||||
|
||||
private ServerViewModel mServerViewModel;
|
||||
private AddServerFragment addServerFragment;
|
||||
private FloatingActionButton floatingActionButton;
|
||||
private FragmentManager fragmentManager;
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
finish(); // close this activity as oppose to navigating up
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_server_address_book);
|
||||
|
||||
// Attaching the layout to the toolbar object
|
||||
Toolbar toolbar = findViewById(R.id.tool_bar_server_address_book);
|
||||
// Setting toolbar as the ActionBar with setSupportActionBar() call
|
||||
setSupportActionBar(toolbar);
|
||||
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_baseline_close_24);
|
||||
|
||||
mServerViewModel = new ViewModelProvider(this).get(ServerViewModel.class);
|
||||
|
||||
showServers();
|
||||
|
||||
floatingActionButton = findViewById(R.id.add_server);
|
||||
floatingActionButton.setOnClickListener(view -> {
|
||||
|
||||
Log.d(TAG, "Click");
|
||||
|
||||
fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
|
||||
addServerFragment = new AddServerFragment();
|
||||
fragmentTransaction.replace(R.id.server_book, addServerFragment);
|
||||
fragmentTransaction.commit();
|
||||
|
||||
floatingActionButton.hide();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentInteraction(Uri uri) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerCaptureChanged(boolean hasCapture) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void showServers()
|
||||
{
|
||||
RecyclerView recyclerView = findViewById(R.id.server_list_recyclerview);
|
||||
final ServerListAdapter adapter = new ServerListAdapter(this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
// Delete items on swipe
|
||||
ItemTouchHelper helper = new ItemTouchHelper(
|
||||
new ItemTouchHelper.SimpleCallback(0,
|
||||
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
int direction) {
|
||||
|
||||
|
||||
new AlertDialog.Builder(ServerAddressBookActivity.this)
|
||||
.setTitle(getString(R.string.server_book_del_alert_title))
|
||||
.setMessage(getString(R.string.server_book_del_alert_msg))
|
||||
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
|
||||
int position = viewHolder.getAdapterPosition();
|
||||
Server server = adapter.getServerAtPosition(position);
|
||||
// Toast.makeText(ServerAddressBookActivity.this, "Deleting " +
|
||||
// server.getServerName(), Toast.LENGTH_LONG).show();
|
||||
// Delete the server
|
||||
mServerViewModel.delete(server);
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, (dialog, which) -> {
|
||||
adapter.notifyItemChanged(viewHolder.getAdapterPosition());
|
||||
})
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show();
|
||||
|
||||
}
|
||||
});
|
||||
helper.attachToRecyclerView(recyclerView);
|
||||
|
||||
|
||||
// Update the cached copy of the words in the adapter.
|
||||
mServerViewModel.getAllServers().observe(this, adapter::setServers);
|
||||
|
||||
}
|
||||
|
||||
public void addServer(View view)
|
||||
{
|
||||
Log.d(TAG, "addServer");
|
||||
|
||||
EditText serverLabel = view.findViewById(R.id.serverLabel);
|
||||
EditText serverUrl = view.findViewById(R.id.serverUrl);
|
||||
EditText serverUsername = view.findViewById(R.id.serverUsername);
|
||||
EditText serverPassword = view.findViewById(R.id.serverPassword);
|
||||
|
||||
Server server = new Server(serverLabel.getText().toString());
|
||||
|
||||
server.setServerHost(serverUrl.getText().toString());
|
||||
server.setUsername(serverUsername.getText().toString());
|
||||
server.setPassword(serverPassword.getText().toString());
|
||||
|
||||
mServerViewModel.insert(server);
|
||||
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
fragmentTransaction.remove(addServerFragment);
|
||||
fragmentTransaction.commit();
|
||||
|
||||
floatingActionButton.show();
|
||||
|
||||
}
|
||||
|
||||
public void testServer()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.activity
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.adapter.ServerListAdapter
|
||||
import net.schueller.peertube.database.Server
|
||||
import net.schueller.peertube.database.ServerViewModel
|
||||
import net.schueller.peertube.databinding.ActivityServerAddressBookBinding
|
||||
import net.schueller.peertube.fragment.AddServerFragment
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import net.schueller.peertube.network.Session
|
||||
import net.schueller.peertube.service.LoginService
|
||||
import java.util.*
|
||||
|
||||
class ServerAddressBookActivity : CommonActivity() {
|
||||
|
||||
private val TAG = "ServerAddressBookActivity"
|
||||
|
||||
private val mServerViewModel: ServerViewModel by viewModels()
|
||||
private var addServerFragment: AddServerFragment? = null
|
||||
|
||||
private val fragmentManager: FragmentManager by lazy { supportFragmentManager }
|
||||
|
||||
|
||||
private lateinit var mBinding: ActivityServerAddressBookBinding
|
||||
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
finish() // close this activity as oppose to navigating up
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mBinding = ActivityServerAddressBookBinding.inflate(layoutInflater)
|
||||
setContentView(mBinding.root)
|
||||
|
||||
// Setting toolbar as the ActionBar with setSupportActionBar() call
|
||||
setSupportActionBar(mBinding.toolBarServerAddressBook)
|
||||
supportActionBar?.apply {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setHomeAsUpIndicator(R.drawable.ic_baseline_close_24)
|
||||
}
|
||||
|
||||
|
||||
showServers()
|
||||
|
||||
mBinding.addServer.setOnClickListener {
|
||||
Log.d(TAG, "Click")
|
||||
|
||||
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||
addServerFragment = AddServerFragment().also {
|
||||
fragmentTransaction.replace(R.id.server_book, it)
|
||||
fragmentTransaction.commit()
|
||||
mBinding.addServer.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onServerClick(server: Server) {
|
||||
|
||||
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val editor = sharedPref.edit()
|
||||
val serverUrl = APIUrlHelper.cleanServerUrl(server.serverHost)
|
||||
editor.putString(getString(R.string.pref_api_base_key), serverUrl)
|
||||
editor.apply()
|
||||
|
||||
// Logout if logged in
|
||||
val session = Session.getInstance()
|
||||
if (session.isLoggedIn) {
|
||||
session.invalidate()
|
||||
}
|
||||
|
||||
// attempt authentication if we have a username
|
||||
if (server.username.isNullOrBlank().not()) {
|
||||
LoginService.Authenticate(server.username, server.password)
|
||||
}
|
||||
|
||||
// close this activity
|
||||
finish()
|
||||
Toast.makeText(this, getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
private fun onEditClick(server: Server) {
|
||||
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||
addServerFragment = AddServerFragment.newInstance(server).also {
|
||||
fragmentTransaction.replace(R.id.server_book, it)
|
||||
fragmentTransaction.commit()
|
||||
mBinding.addServer.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showServers() {
|
||||
val adapter = ServerListAdapter(mutableListOf(), { onServerClick(it) }, { onEditClick(it) }).also {
|
||||
mBinding.serverListRecyclerview.adapter = it
|
||||
}
|
||||
|
||||
// Delete items on swipe
|
||||
val helper = ItemTouchHelper(
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
AlertDialog.Builder(this@ServerAddressBookActivity)
|
||||
.setTitle(getString(R.string.server_book_del_alert_title))
|
||||
.setMessage(getString(R.string.server_book_del_alert_msg))
|
||||
.setPositiveButton(android.R.string.yes) { _: DialogInterface?, _: Int ->
|
||||
val position = viewHolder.adapterPosition
|
||||
val server = adapter.getServerAtPosition(position)
|
||||
// Toast.makeText(ServerAddressBookActivity.this, "Deleting " +
|
||||
// server.getServerName(), Toast.LENGTH_LONG).show();
|
||||
// Delete the server
|
||||
mServerViewModel.delete(server)
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.adapterPosition) }
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
helper.attachToRecyclerView(mBinding.serverListRecyclerview)
|
||||
|
||||
|
||||
// Update the cached copy of the words in the adapter.
|
||||
mServerViewModel.allServers.observe(this, { servers: List<Server> ->
|
||||
adapter.setServers(servers)
|
||||
|
||||
addServerFragment?.let {
|
||||
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||
fragmentTransaction.remove(it)
|
||||
fragmentTransaction.commit()
|
||||
mBinding.addServer.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_REPLY = "net.schueller.peertube.room.REPLY"
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
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.database.Server;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.network.Session;
|
||||
import net.schueller.peertube.service.LoginService;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class ServerListAdapter extends RecyclerView.Adapter<ServerListAdapter.ServerViewHolder> {
|
||||
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private List<Server> mServers; // Cached copy of Servers
|
||||
|
||||
public ServerListAdapter(Context context) {
|
||||
this.mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ServerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = mInflater.inflate(R.layout.row_server_address_book, 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(mInflater.getContext().getString(R.string.pref_api_base_key), serverUrl);
|
||||
editor.apply();
|
||||
|
||||
// Logout if logged in
|
||||
Session session = Session.getInstance();
|
||||
if (session.isLoggedIn()) {
|
||||
session.invalidate();
|
||||
}
|
||||
|
||||
// attempt authentication if we have a username
|
||||
if (!TextUtils.isEmpty(getServerAtPosition(position).getUsername())) {
|
||||
LoginService.Authenticate(
|
||||
getServerAtPosition(position).getUsername(),
|
||||
getServerAtPosition(position).getPassword()
|
||||
);
|
||||
}
|
||||
|
||||
// tell server list activity to reload list
|
||||
Intent intent = new Intent();
|
||||
((Activity) mInflater.getContext()).setResult(RESULT_OK, intent);
|
||||
|
||||
// close this activity
|
||||
((Activity) mInflater.getContext()).finish();
|
||||
|
||||
Toast.makeText(mInflater.getContext(), mInflater.getContext().getString(R.string.server_selection_set_server, serverUrl), Toast.LENGTH_LONG).show();
|
||||
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// holder.itemView.setOnLongClickListener(v -> {
|
||||
// Log.v("ServerListAdapter", "setOnLongClickListener " + position);
|
||||
// return true;
|
||||
// });
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void setServers(List<Server> Servers) {
|
||||
mServers = Servers;
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// getItemCount() is called many times, and when it is first called,
|
||||
// mServers has not been updated (means initially, it's null, and we can't return null).
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (mServers != null)
|
||||
return mServers.size();
|
||||
else return 0;
|
||||
}
|
||||
|
||||
static class ServerViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView serverLabel, serverUrl, serverUsername;
|
||||
ImageView hasLogin;
|
||||
|
||||
private ServerViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
serverLabel = itemView.findViewById(R.id.serverLabelRow);
|
||||
serverUrl = itemView.findViewById(R.id.serverUrlRow);
|
||||
hasLogin = itemView.findViewById(R.id.sb_row_has_login_icon);
|
||||
}
|
||||
}
|
||||
|
||||
public Server getServerAtPosition (int position) {
|
||||
return mServers.get(position);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import net.schueller.peertube.adapter.ServerListAdapter.ServerViewHolder
|
||||
import net.schueller.peertube.database.Server
|
||||
import net.schueller.peertube.databinding.RowServerAddressBookBinding
|
||||
import net.schueller.peertube.utils.visibleIf
|
||||
|
||||
class ServerListAdapter(private val mServers: MutableList<Server>, private val onClick: (Server) -> Unit, private val onEditClick: (Server) -> Unit) : RecyclerView.Adapter<ServerViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ServerViewHolder {
|
||||
|
||||
val binding = RowServerAddressBookBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
|
||||
return ServerViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ServerViewHolder, position: Int) {
|
||||
holder.bind(mServers[position])
|
||||
}
|
||||
|
||||
fun setServers(servers: List<Server>) {
|
||||
mServers.clear()
|
||||
mServers.addAll(servers)
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mServers.size
|
||||
}
|
||||
|
||||
inner class ServerViewHolder (private val binding: RowServerAddressBookBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(server: Server) {
|
||||
|
||||
binding.serverLabelRow.text = server.serverName
|
||||
binding.serverUrlRow.text = server.serverHost
|
||||
binding.sbRowHasLoginIcon.visibleIf { server.username.isNullOrBlank().not() }
|
||||
|
||||
binding.root.setOnClickListener { onClick(server) }
|
||||
binding.editIcon.setOnClickListener { onEditClick(server) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getServerAtPosition(position: Int): Server {
|
||||
return mServers[position]
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.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;
|
||||
}
|
||||
}
|
@ -14,31 +14,31 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.database;
|
||||
package net.schueller.peertube.database
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Parcelable
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
@Parcelize
|
||||
@Entity(tableName = "server_table")
|
||||
data class Server(
|
||||
|
||||
import java.util.List;
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Int = 0,
|
||||
|
||||
public class ServerViewModel extends AndroidViewModel {
|
||||
@ColumnInfo(name = "server_name")
|
||||
var serverName: String,
|
||||
|
||||
private ServerRepository mRepository;
|
||||
@ColumnInfo(name = "server_host")
|
||||
var serverHost: String? = null,
|
||||
|
||||
private LiveData<List<Server>> mAllServers;
|
||||
@ColumnInfo(name = "username")
|
||||
var username: String? = null,
|
||||
|
||||
public ServerViewModel (Application application) {
|
||||
super(application);
|
||||
mRepository = new ServerRepository(application);
|
||||
mAllServers = mRepository.getAllServers();
|
||||
}
|
||||
@ColumnInfo(name = "password")
|
||||
var password: String? = null
|
||||
|
||||
public LiveData<List<Server>> getAllServers() { return mAllServers; }
|
||||
|
||||
public void insert(Server server) { mRepository.insert(server); }
|
||||
|
||||
public void delete(Server server) {mRepository.delete(server);}
|
||||
|
||||
}
|
||||
) : Parcelable
|
@ -14,29 +14,26 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.database;
|
||||
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;
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
|
||||
@Dao
|
||||
public interface ServerDao {
|
||||
interface ServerDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(Server server);
|
||||
@Insert
|
||||
suspend fun insert(server: Server)
|
||||
|
||||
@Update
|
||||
suspend fun update(server: Server)
|
||||
|
||||
@Query("DELETE FROM server_table")
|
||||
void deleteAll();
|
||||
suspend fun deleteAll()
|
||||
|
||||
@Delete
|
||||
void delete(Server server);
|
||||
suspend fun delete(server: Server)
|
||||
|
||||
@Query("SELECT * from server_table ORDER BY server_name DESC")
|
||||
LiveData<List<Server>> getAllServers();
|
||||
@get:Query("SELECT * from server_table ORDER BY server_name DESC")
|
||||
val allServers: LiveData<List<Server>>
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.database;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class ServerRepository {
|
||||
|
||||
private ServerDao mServerDao;
|
||||
private LiveData<List<Server>> mAllServers;
|
||||
|
||||
ServerRepository(Application application) {
|
||||
ServerRoomDatabase db = ServerRoomDatabase.getDatabase(application);
|
||||
mServerDao = db.serverDao();
|
||||
mAllServers = mServerDao.getAllServers();
|
||||
|
||||
}
|
||||
|
||||
LiveData<List<Server>> getAllServers() {
|
||||
return mAllServers;
|
||||
}
|
||||
|
||||
void insert (Server server) {
|
||||
new insertAsyncTask(mServerDao).execute(server);
|
||||
}
|
||||
|
||||
public void delete(Server server) {
|
||||
new deleteServerAsyncTask(mServerDao).execute(server);
|
||||
}
|
||||
|
||||
private static class insertAsyncTask extends AsyncTask<Server, Void, Void> {
|
||||
|
||||
private ServerDao mAsyncTaskDao;
|
||||
|
||||
insertAsyncTask(ServerDao dao) {
|
||||
mAsyncTaskDao = dao;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(final Server... params) {
|
||||
mAsyncTaskDao.insert(params[0]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class deleteServerAsyncTask extends AsyncTask<Server, Void, Void> {
|
||||
private ServerDao mAsyncTaskDao;
|
||||
|
||||
deleteServerAsyncTask(ServerDao dao) {
|
||||
mAsyncTaskDao = dao;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(final Server... params) {
|
||||
mAsyncTaskDao.delete(params[0]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.database
|
||||
|
||||
import android.app.Application
|
||||
import android.os.AsyncTask
|
||||
import androidx.lifecycle.LiveData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class ServerRepository(application: Application) {
|
||||
|
||||
private val mServerDao: ServerDao
|
||||
|
||||
val allServers: LiveData<List<Server>>
|
||||
get() = mServerDao.allServers
|
||||
|
||||
init {
|
||||
val db = ServerRoomDatabase.getDatabase(application)
|
||||
mServerDao = db.serverDao()
|
||||
}
|
||||
|
||||
suspend fun update(server: Server) = withContext(Dispatchers.IO) {
|
||||
mServerDao.update(server)
|
||||
}
|
||||
|
||||
suspend fun insert(server: Server) = withContext(Dispatchers.IO) {
|
||||
mServerDao.insert(server)
|
||||
}
|
||||
|
||||
suspend fun delete(server: Server) = withContext(Dispatchers.IO){
|
||||
mServerDao.delete(server)
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.database
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ServerViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val mRepository: ServerRepository = ServerRepository(application)
|
||||
val allServers: LiveData<List<Server>> = mRepository.allServers
|
||||
|
||||
fun insert(server: Server) {
|
||||
viewModelScope.launch {
|
||||
mRepository.insert(server)
|
||||
}
|
||||
}
|
||||
|
||||
fun update(server: Server) {
|
||||
viewModelScope.launch {
|
||||
mRepository.update(server)
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(server: Server) {
|
||||
viewModelScope.launch {
|
||||
mRepository.delete(server)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.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.SearchServerActivity;
|
||||
import net.schueller.peertube.activity.ServerAddressBookActivity;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
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 {
|
||||
assert act != null;
|
||||
InputMethodManager inputManager = (InputMethodManager)
|
||||
act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
inputManager.hideSoftInputFromWindow(Objects.requireNonNull(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(), SearchServerActivity.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);
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.schueller.peertube.fragment
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import net.schueller.peertube.R
|
||||
import net.schueller.peertube.activity.SearchServerActivity
|
||||
import net.schueller.peertube.database.Server
|
||||
import net.schueller.peertube.database.ServerViewModel
|
||||
import net.schueller.peertube.databinding.FragmentAddServerBinding
|
||||
import net.schueller.peertube.helper.APIUrlHelper
|
||||
import net.schueller.peertube.utils.hideKeyboard
|
||||
|
||||
class AddServerFragment : Fragment() {
|
||||
|
||||
|
||||
private lateinit var mBinding: FragmentAddServerBinding
|
||||
|
||||
private val mServerViewModel: ServerViewModel by activityViewModels()
|
||||
|
||||
private var mServer: Server? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let {
|
||||
mServer = it.getParcelable(SERVER_ARG)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
mBinding = FragmentAddServerBinding.inflate(inflater, container, false)
|
||||
return mBinding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
initServerEdit()
|
||||
|
||||
mBinding.addServerButton.setOnClickListener {
|
||||
var formValid = true
|
||||
|
||||
hideKeyboard()
|
||||
|
||||
if (mBinding.serverLabel.text.toString().isBlank()) {
|
||||
mBinding.serverLabel.error = getString(R.string.server_book_label_is_required)
|
||||
Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_LONG).show()
|
||||
formValid = false
|
||||
}
|
||||
|
||||
// validate url
|
||||
mBinding.serverUrl.apply {
|
||||
APIUrlHelper.cleanServerUrl(text.toString())?.let {
|
||||
|
||||
setText(it)
|
||||
|
||||
if (!Patterns.WEB_URL.matcher(it).matches()) {
|
||||
error = getString(R.string.server_book_valid_url_is_required)
|
||||
Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_LONG).show()
|
||||
formValid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (formValid) {
|
||||
mServer?.apply {
|
||||
mBinding.let {
|
||||
serverName = it.serverLabel.text.toString()
|
||||
serverHost = it.serverUrl.text.toString()
|
||||
username = it.serverUsername.text.toString()
|
||||
password = it.serverPassword.text.toString()
|
||||
|
||||
mServerViewModel.update(this)
|
||||
}
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
mBinding.apply {
|
||||
val server = Server(serverName = serverLabel.text.toString())
|
||||
|
||||
server.serverHost = serverUrl.text.toString()
|
||||
server.username = serverUsername.text.toString()
|
||||
server.password = serverPassword.text.toString()
|
||||
|
||||
mServerViewModel.insert(server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mBinding.pickServerUrl.setOnClickListener {
|
||||
val intentServer = Intent(activity, SearchServerActivity::class.java)
|
||||
this.startActivityForResult(intentServer, PICK_SERVER)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initServerEdit() {
|
||||
mServer?.let {
|
||||
mBinding.apply {
|
||||
serverLabel.setText(it.serverName)
|
||||
serverUrl.setText(it.serverHost)
|
||||
serverUsername.setText(it.username)
|
||||
serverPassword.setText(it.password)
|
||||
|
||||
addServerButton.text = getString(R.string.server_book_add_save_button)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
if (requestCode != PICK_SERVER) {
|
||||
return
|
||||
}
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return
|
||||
}
|
||||
|
||||
val serverUrlTest = data?.getStringExtra("serverUrl")
|
||||
//Log.d(TAG, "serverUrl " + serverUrlTest);
|
||||
|
||||
mBinding.serverUrl.setText(serverUrlTest)
|
||||
|
||||
mBinding.serverLabel.apply {
|
||||
if (text.toString().isBlank()) {
|
||||
setText(data?.getStringExtra("serverName"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AddServerFragment"
|
||||
private const val PICK_SERVER = 1
|
||||
|
||||
private const val SERVER_ARG = "server"
|
||||
|
||||
fun newInstance(server: Server) = AddServerFragment().apply {
|
||||
arguments = Bundle().also {
|
||||
it.putParcelable(SERVER_ARG, server)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
58
app/src/main/java/net/schueller/peertube/utils/Extensions.kt
Normal file
58
app/src/main/java/net/schueller/peertube/utils/Extensions.kt
Normal file
@ -0,0 +1,58 @@
|
||||
package net.schueller.peertube.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
fun View.gone() {
|
||||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun View.visible() {
|
||||
this.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun View.invisible() {
|
||||
this.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
fun View.visibleIf(predicate: () -> Boolean) {
|
||||
when (predicate.invoke()) {
|
||||
true -> visible()
|
||||
else -> gone()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.goneIf(predicate: () -> Boolean) {
|
||||
when (predicate.invoke()) {
|
||||
true -> gone()
|
||||
else -> visible()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.invisibleIf(predicate: () -> Boolean) {
|
||||
when (predicate.invoke()) {
|
||||
true -> invisible()
|
||||
else -> visible()
|
||||
}
|
||||
}
|
||||
|
||||
fun Fragment.hideKeyboard(): Boolean {
|
||||
activity?.currentFocus?.let {
|
||||
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(it.windowToken, 0)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun AppCompatActivity.hideKeyboard(): Boolean {
|
||||
currentFocus?.let {
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(it.windowToken, 0)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
10
app/src/main/res/drawable/ic_edit_24.xml
Normal file
10
app/src/main/res/drawable/ic_edit_24.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
</vector>
|
@ -31,7 +31,25 @@
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
app:srcCompat="@drawable/ic_baseline_add_24" />
|
||||
|
||||
<include layout="@layout/content_server_address_book" />
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
>
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/server_list_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/darker_gray"
|
||||
tools:listitem="@layout/row_server_address_book"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:context=".activity.ServerAddressBookActivity"
|
||||
tools:showIn="@layout/activity_server_address_book">
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/server_list_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/darker_gray"
|
||||
tools:listitem="@layout/row_server_address_book" />
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -19,28 +19,29 @@
|
||||
android:hint="@string/server_book_add_label"
|
||||
android:inputType="textPersonName" />
|
||||
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content">
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<EditText
|
||||
android:layout_alignParentStart="true"
|
||||
android:id="@+id/serverUrl"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ems="10"
|
||||
android:hint="@string/server_book_add_server_url"
|
||||
android:inputType="textUri"
|
||||
android:layout_toStartOf="@+id/pickServerUrl"/>
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_alignParentEnd="true"
|
||||
android:id="@+id/pickServerUrl"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_book_add_pick_server_button" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/serverUsername"
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
@ -9,43 +11,52 @@
|
||||
card_view:cardElevation="0dp"
|
||||
card_view:cardUseCompatPadding="true">
|
||||
|
||||
<RelativeLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_width="fill_parent"
|
||||
<TextView
|
||||
android:id="@+id/serverLabelRow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
card_view:layout_constraintTop_toTopOf="parent"
|
||||
card_view:layout_constraintStart_toStartOf="parent"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline"
|
||||
tools:text="@tools:sample/lorem"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/serverLabelRow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/serverUrlRow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="0dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/serverUrlRow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
card_view:layout_constraintTop_toBottomOf="@id/serverLabelRow"
|
||||
card_view:layout_constraintStart_toStartOf="parent"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead"
|
||||
tools:text="@tools:sample/lorem"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sb_row_has_login_icon"
|
||||
android:src="@drawable/ic_baseline_account_circle_24"
|
||||
android:visibility="visible"
|
||||
android:contentDescription="@string/server_book_list_has_login"
|
||||
android:layout_width="24dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_height="wrap_content"/>
|
||||
android:layout_height="wrap_content"
|
||||
card_view:layout_constraintTop_toTopOf="parent"
|
||||
card_view:layout_constraintEnd_toEndOf="parent"
|
||||
android:contentDescription="@string/server_book_list_has_login"
|
||||
android:src="@drawable/ic_baseline_account_circle_24"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
<ImageView
|
||||
android:id="@+id/edit_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
card_view:layout_constraintTop_toBottomOf="@id/sb_row_has_login_icon"
|
||||
card_view:layout_constraintEnd_toEndOf="parent"
|
||||
card_view:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="@string/server_book_list_has_login"
|
||||
android:src="@drawable/ic_edit_24"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
@ -335,6 +335,7 @@
|
||||
<string name="server_book_add_username">Username</string>
|
||||
<string name="server_book_add_password">Password</string>
|
||||
<string name="server_book_add_add_button">Add</string>
|
||||
<string name="server_book_add_save_button">Save</string>
|
||||
<string name="server_book_list_has_login">Has Login</string>
|
||||
<string name="login_current_server_hint">Current Server</string>
|
||||
<string name="title_activity_server_address_book">Address Book</string>
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
ext.kotlin_version = '1.4.21'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
@ -8,6 +10,7 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
@ -1 +0,0 @@
|
||||
- Authentifizierungsaktualisierung
|
@ -1,33 +0,0 @@
|
||||
Thorium ist ein PeerTube-Client, der eine Verbindung zu jedem PeerTube-Server mit Version v1.1.0-alpha.2 oder höher herstellen kann.
|
||||
|
||||
PeerTube ist eine föderierte (ActivityPub) Video-Streaming-Plattform, die P2P (BitTorrent) direkt im Webbrowser verwendet. Weitere Informationen finden Sie unter https://joinpeertube.org/ und eine Liste von Servern.
|
||||
|
||||
Dieser Client wird vorkonfiguriert mit einem PeerTube-Server geliefert, der vom Ersteller der Anwendung verwaltet wird – nicht das PeerTube-Projekt selbst, das mehr unter http://instances.joinpeertube.org/ aufgelistet ist –, damit Sie einen Vorgeschmack darauf bekommen, wozu der Client in der Lage ist. Wählen Sie Ihren Server, um Ihre Erfahrung zu optimieren!
|
||||
|
||||
Aktuelle Merkmale:
|
||||
- Verbindung zu jedem PeerTube-Server
|
||||
- Torrent-Video oder direkte Wiedergabe
|
||||
- PeerTube durchsuchen
|
||||
- Video herunterladen / weitergeben
|
||||
- Dunkel-Modus
|
||||
- Wiedergabe im Hintergrund
|
||||
- Vollbild-Wiedergabe im Querformat
|
||||
- Wiedergabegeschwindigkeit
|
||||
- NSFW-Inhalt filtern
|
||||
- Authentifizierung / Anmeldung
|
||||
- Video gefällt mir / gefällt mir nicht
|
||||
|
||||
Demnächst:
|
||||
- Videos kommentieren
|
||||
- Registrieren
|
||||
- Benutzer-/Kanal-Übersichtsseite
|
||||
- Bericht Videos
|
||||
|
||||
Genehmigungen:
|
||||
- Speicherzugang, erforderlich für Torrent-Herunterladen oder Video-Herunterladen.
|
||||
|
||||
Lizenziert unter der GNU Affero General Public License v3.0
|
||||
|
||||
Genehmigungen dieser stärksten Copyleft-Lizenz sind an die Bedingung geknüpft, den vollständigen Quellcode der lizenzierten Werke und Modifikationen, zu denen auch größere Werke gehören, die ein lizenziertes Werk verwenden, unter der gleichen Lizenz zur Verfügung zu stellen. Urheberrechts- und Lizenzhinweise müssen erhalten bleiben. Mitwirkende stellen eine ausdrückliche Gewährung von Patentrechten zur Verfügung. Wenn eine modifizierte Version verwendet wird, um einen Dienst über ein Netzwerk anzubieten, muss der vollständige Quellcode der modifizierten Version zur Verfügung gestellt werden.
|
||||
|
||||
Quellcode unter: https://github.com/sschueller/peertube-android/
|
@ -1 +0,0 @@
|
||||
Thorium ist ein inoffizieller PeerTube-Abspieler
|
@ -1 +0,0 @@
|
||||
Inoffizieller PeerTube-Client
|
@ -1 +0,0 @@
|
||||
- Aggiornamento dell'autenticazione
|
@ -1 +0,0 @@
|
||||
- uscita F-Droid per correggere la distribuzione automatica
|
@ -1,7 +0,0 @@
|
||||
- Aggiunto il supporto del reindirizzamento ipertestuale nella descrizione (@freeboub)
|
||||
- vari crash fix (@freeboub)
|
||||
- evitare di andare a pip quando si esce dall'applicazione a causa del pulsante di condivisione (@freeboub)
|
||||
- Aggiunto la possibilità di filtrare la lista dei server (@freeboub)
|
||||
- Gestione degli errori del rifattore Toast per dividere l'errore di rete (@freeboub)
|
||||
- Mantenere il rapporto d'aspetto video per pip (@freeboub)
|
||||
- la barra di navigazione non è stata ripristinata
|
@ -1,33 +0,0 @@
|
||||
Thorium è un client PeerTube in grado di connettersi a qualsiasi server peertube che esegue la versione v1.1.0-alpha.2 o successiva.
|
||||
|
||||
PeerTube è una piattaforma di streaming video federata (ActivityPub) che utilizza P2P (BitTorrent) direttamente nel browser web. Per ulteriori informazioni, visitare https://joinpeertube.org/ per ulteriori informazioni e un elenco dei server.
|
||||
|
||||
Questo client viene preconfigurato con un server PeerTube gestito dal creatore dell'applicazione – non il progetto PeerTube stesso, che elenca di più su http://instances.joinpeertube.org/ – per consentire di avere un assaggio di ciò che il client è in grado di. Scegli il tuo server per ottimizzare la tua esperienza!
|
||||
|
||||
Caratteristiche attuali:
|
||||
- Connettersi a qualsiasi server PeerTube
|
||||
- Video Torrent o riproduzione diretta
|
||||
- Ricerca PeerTube
|
||||
- Scarica / Condividi video
|
||||
- Temi / Modalità scuro
|
||||
- Riproduzione nello sfondo
|
||||
- Riproduzione a schermo intero in orizzontale
|
||||
- Velocità di riproduzione
|
||||
- Filtrare il contenuto NSFW
|
||||
- Autenticazione / accesso
|
||||
- Video mi piace /non mi piace
|
||||
|
||||
Comming Presto:
|
||||
- Commenta i video
|
||||
- Registrati
|
||||
- Pagina Panoramica utente/canale
|
||||
- Segnala video
|
||||
|
||||
Autorizzazioni:
|
||||
- Accesso di archiviazione, necessario per lo scaricamento Torrent o lo scaricamento video.
|
||||
|
||||
Concesso in licenza ai sensi della GNU Affero General Public License v3.0
|
||||
|
||||
Le autorizzazioni di questa licenza copyleft più forte sono condizionate a rendere disponibile il codice sorgente completo delle opere e delle modifiche con licenza, che includono opere più grandi utilizzando un lavoro concesso in licenza, sotto la stessa licenza. Le note sul copyright e sulla licenza devono essere conservate. I contributori forniscono una concessione espressa dei diritti di brevetto. Quando una versione modificata viene utilizzata per fornire un servizio in rete, è necessario reso disponibile il codice sorgente completo della versione modificata.
|
||||
|
||||
Codice sorgente all'indirizzo: https://github.com/sschueller/peertube-android/
|
@ -1 +0,0 @@
|
||||
Thorium è un lettore PeerTube non ufficiale
|
@ -1 +0,0 @@
|
||||
Cliente PeerTube non ufficiale
|
@ -1 +0,0 @@
|
||||
https://www.youtube.com/watch?v=PJIsiuSdpq8
|
@ -1 +0,0 @@
|
||||
https://www.youtube.com/watch?v=PJIsiuSdpq8
|
Loading…
Reference in New Issue
Block a user