diff --git a/app/build.gradle b/app/build.gradle index 7948ec1..ef4901c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.java b/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.java deleted file mode 100644 index f7e0514..0000000 --- a/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * 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 . - */ -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() - { - - } - -} diff --git a/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.kt b/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.kt new file mode 100644 index 0000000..3c55cfa --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * 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 . + */ +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 -> + 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.java b/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.java deleted file mode 100644 index 06bd053..0000000 --- a/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * 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 . - */ -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 { - - - 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_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 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/adapter/ServerListAdapter.kt b/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.kt new file mode 100644 index 0000000..e749ffe --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/adapter/ServerListAdapter.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * 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 . + */ +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, private val onClick: (Server) -> Unit, private val onEditClick: (Server) -> Unit) : RecyclerView.Adapter() { + + 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) { + 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] + } +} \ 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 deleted file mode 100644 index 5240e27..0000000 --- a/app/src/main/java/net/schueller/peertube/database/Server.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * 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 . - */ -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/ServerViewModel.java b/app/src/main/java/net/schueller/peertube/database/Server.kt similarity index 50% rename from app/src/main/java/net/schueller/peertube/database/ServerViewModel.java rename to app/src/main/java/net/schueller/peertube/database/Server.kt index 19044e0..19f9d7f 100644 --- a/app/src/main/java/net/schueller/peertube/database/ServerViewModel.java +++ b/app/src/main/java/net/schueller/peertube/database/Server.kt @@ -14,31 +14,31 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -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> 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> getAllServers() { return mAllServers; } - - public void insert(Server server) { mRepository.insert(server); } - - public void delete(Server server) {mRepository.delete(server);} - -} +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/ServerDao.java b/app/src/main/java/net/schueller/peertube/database/ServerDao.kt similarity index 60% rename from app/src/main/java/net/schueller/peertube/database/ServerDao.java rename to app/src/main/java/net/schueller/peertube/database/ServerDao.kt index 4c6a565..a8d42ee 100644 --- a/app/src/main/java/net/schueller/peertube/database/ServerDao.java +++ b/app/src/main/java/net/schueller/peertube/database/ServerDao.kt @@ -14,29 +14,26 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -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> getAllServers(); + @get:Query("SELECT * from server_table ORDER BY server_name DESC") + val allServers: LiveData> } \ 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 deleted file mode 100644 index 7aa7c86..0000000 --- a/app/src/main/java/net/schueller/peertube/database/ServerRepository.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * 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 . - */ -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/ServerRepository.kt b/app/src/main/java/net/schueller/peertube/database/ServerRepository.kt new file mode 100644 index 0000000..871b9a9 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/ServerRepository.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * 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 . + */ +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> + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/ServerViewModel.kt b/app/src/main/java/net/schueller/peertube/database/ServerViewModel.kt new file mode 100644 index 0000000..da17ef9 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/ServerViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * 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 . + */ +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> = 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.java b/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.java deleted file mode 100644 index bc2b3fd..0000000 --- a/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * 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 . - */ -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); - } -} diff --git a/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.kt b/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.kt new file mode 100644 index 0000000..2bfc3be --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * 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 . + */ +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) + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/utils/Extensions.kt b/app/src/main/java/net/schueller/peertube/utils/Extensions.kt new file mode 100644 index 0000000..6a4600a --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/utils/Extensions.kt @@ -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 +} diff --git a/app/src/main/res/drawable/ic_edit_24.xml b/app/src/main/res/drawable/ic_edit_24.xml new file mode 100644 index 0000000..2844baf --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_server_address_book.xml b/app/src/main/res/layout/activity_server_address_book.xml index 5de5eb8..d09f244 100644 --- a/app/src/main/res/layout/activity_server_address_book.xml +++ b/app/src/main/res/layout/activity_server_address_book.xml @@ -31,7 +31,25 @@ android:layout_margin="@dimen/fab_margin" app:srcCompat="@drawable/ic_baseline_add_24" /> - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_server_address_book.xml b/app/src/main/res/layout/content_server_address_book.xml deleted file mode 100644 index cd9daa2..0000000 --- a/app/src/main/res/layout/content_server_address_book.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_server.xml b/app/src/main/res/layout/fragment_add_server.xml index e55bba1..91e0a6b 100644 --- a/app/src/main/res/layout/fragment_add_server.xml +++ b/app/src/main/res/layout/fragment_add_server.xml @@ -19,28 +19,29 @@ android:hint="@string/server_book_add_label" android:inputType="textPersonName" /> - + android:layout_width="match_parent" + android:orientation="horizontal" + > + />