refactored ServerListAdapter and converted it to kotlin

This commit is contained in:
kosharskiy 2021-01-14 09:55:22 +02:00
parent 4f12fd94ff
commit 2a1d2058e3
10 changed files with 274 additions and 291 deletions

View File

@ -16,12 +16,16 @@
*/ */
package net.schueller.peertube.activity package net.schueller.peertube.activity
import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import net.schueller.peertube.R import net.schueller.peertube.R
@ -30,6 +34,9 @@ import net.schueller.peertube.database.Server
import net.schueller.peertube.database.ServerViewModel import net.schueller.peertube.database.ServerViewModel
import net.schueller.peertube.databinding.ActivityServerAddressBookBinding import net.schueller.peertube.databinding.ActivityServerAddressBookBinding
import net.schueller.peertube.fragment.AddServerFragment 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.* import java.util.*
class ServerAddressBookActivity : CommonActivity() { class ServerAddressBookActivity : CommonActivity() {
@ -77,8 +84,32 @@ class ServerAddressBookActivity : CommonActivity() {
} }
} }
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 showServers() { private fun showServers() {
val adapter = ServerListAdapter(this).also { val adapter = ServerListAdapter(mutableListOf()) { onServerClick(it) }.also {
mBinding.serverListRecyclerview.adapter = it mBinding.serverListRecyclerview.adapter = it
} }
@ -110,7 +141,7 @@ class ServerAddressBookActivity : CommonActivity() {
// Update the cached copy of the words in the adapter. // Update the cached copy of the words in the adapter.
mServerViewModel.allServers.observe(this, { servers: List<Server?>? -> mServerViewModel.allServers.observe(this, { servers: List<Server> ->
adapter.setServers(servers) adapter.setServers(servers)
addServerFragment?.let { addServerFragment?.let {

View File

@ -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);
}
}

View File

@ -0,0 +1,75 @@
/*
* 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) : 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])
//
// holder.itemView.setOnLongClickListener(v -> {
// Log.v("ServerListAdapter", "setOnLongClickListener " + position);
// return true;
// });
}
fun setServers(servers: List<Server>) {
mServers.clear()
mServers.addAll(servers)
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 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) }
}
}
fun getServerAtPosition(position: Int): Server {
return mServers[position]
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.os.Parcelable
import androidx.room.PrimaryKey
import androidx.room.ColumnInfo
import androidx.room.Entity
import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(tableName = "server_table")
data class Server(
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
@ColumnInfo(name = "server_name")
var serverName: String,
@ColumnInfo(name = "server_host")
var serverHost: String? = null,
@ColumnInfo(name = "username")
var username: String? = null,
@ColumnInfo(name = "password")
var password: String? = null
) : Parcelable

View File

@ -42,10 +42,17 @@ class AddServerFragment : Fragment() {
private val mServerViewModel: ServerViewModel by activityViewModels() 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? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.d(TAG, "onCreateView")
// Inflate the layout for this fragment
mBinding = FragmentAddServerBinding.inflate(inflater, container, false) mBinding = FragmentAddServerBinding.inflate(inflater, container, false)
return mBinding.root return mBinding.root
} }
@ -53,22 +60,20 @@ class AddServerFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// bind button click initServerEdit()
mBinding.addServerButton.setOnClickListener { view: View? -> mBinding.addServerButton.setOnClickListener {
var formValid = true var formValid = true
hideKeyboard() hideKeyboard()
if (mBinding.serverLabel.text.toString().isBlank()) {
if (mBinding.serverLabel.text.toString().isNullOrBlank()) {
mBinding.serverLabel.error = getString(R.string.server_book_label_is_required) mBinding.serverLabel.error = getString(R.string.server_book_label_is_required)
Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_LONG).show()
formValid = false formValid = false
} }
// validate url // validate url
mBinding.serverUrl.apply { mBinding.serverUrl.apply {
APIUrlHelper.cleanServerUrl(text.toString())?.let { APIUrlHelper.cleanServerUrl(text.toString())?.let {
@ -85,7 +90,7 @@ class AddServerFragment : Fragment() {
if (formValid) { if (formValid) {
mBinding.apply { mBinding.apply {
val server = Server(serverLabel.text.toString()) val server = Server(serverName = serverLabel.text.toString())
server.serverHost = serverUrl.text.toString() server.serverHost = serverUrl.text.toString()
server.username = serverUsername.text.toString() server.username = serverUsername.text.toString()
@ -96,21 +101,26 @@ class AddServerFragment : Fragment() {
} }
} }
// Button testServerButton = mView.findViewById(R.id.testServerButton);
// testServerButton.setOnClickListener(view -> {
// Activity act = getActivity();
// if (act instanceof ServerAddressBookActivity) {
// ((ServerAddressBookActivity) act).testServer();
// }
// });
mBinding.pickServerUrl.setOnClickListener { mBinding.pickServerUrl.setOnClickListener {
val intentServer = Intent(activity, SearchServerActivity::class.java) val intentServer = Intent(activity, SearchServerActivity::class.java)
this.startActivityForResult(intentServer, PICK_SERVER) 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?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
@ -136,7 +146,16 @@ class AddServerFragment : Fragment() {
} }
companion object { companion object {
const val TAG = "AddServerFragment" private const val TAG = "AddServerFragment"
const val PICK_SERVER = 1 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)
}
}
} }
} }

View File

@ -1,10 +1,44 @@
package net.schueller.peertube.utils package net.schueller.peertube.utils
import android.content.Context import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment 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 { fun Fragment.hideKeyboard(): Boolean {
activity?.currentFocus?.let { activity?.currentFocus?.let {
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager

View 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>

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-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:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="true" android:clickable="true"
@ -9,43 +11,52 @@
card_view:cardElevation="0dp" card_view:cardElevation="0dp"
card_view:cardUseCompatPadding="true"> card_view:cardUseCompatPadding="true">
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:padding="12dp"> android:padding="12dp">
<LinearLayout <TextView
android:layout_alignParentStart="true" android:id="@+id/serverLabelRow"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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 <TextView
android:id="@+id/serverLabelRow" android:id="@+id/serverUrlRow"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline" /> card_view:layout_constraintTop_toBottomOf="@id/serverLabelRow"
card_view:layout_constraintStart_toStartOf="parent"
<TextView android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead"
android:id="@+id/serverUrlRow" tools:text="@tools:sample/lorem"
android:layout_width="wrap_content" />
android:layout_height="wrap_content"
android:paddingTop="0dp"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<ImageView <ImageView
android:id="@+id/sb_row_has_login_icon" 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_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> </androidx.cardview.widget.CardView>

View File

@ -335,6 +335,7 @@
<string name="server_book_add_username">Username</string> <string name="server_book_add_username">Username</string>
<string name="server_book_add_password">Password</string> <string name="server_book_add_password">Password</string>
<string name="server_book_add_add_button">Add</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="server_book_list_has_login">Has Login</string>
<string name="login_current_server_hint">Current Server</string> <string name="login_current_server_hint">Current Server</string>
<string name="title_activity_server_address_book">Address Book</string> <string name="title_activity_server_address_book">Address Book</string>