Refactor instance administration - unadopted repositories

This commit is contained in:
M M Arif
2026-03-25 19:58:12 +05:00
parent ea491bb322
commit d36061353e
9 changed files with 316 additions and 571 deletions

View File

@@ -39,9 +39,6 @@
<activity
android:name=".activities.AdminGetUsersActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.AdminUnadoptedReposActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.CreateReleaseActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>

View File

@@ -1,119 +0,0 @@
package org.mian.gitnex.activities;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import org.mian.gitnex.R;
import org.mian.gitnex.adapters.AdminUnadoptedReposAdapter;
import org.mian.gitnex.databinding.ActivityAdminCronTasksBinding;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.viewmodels.AdminUnadoptedReposViewModel;
/**
* @author mmarif
* @author qwerty287
*/
public class AdminUnadoptedReposActivity extends BaseActivity {
private AdminUnadoptedReposViewModel viewModel;
private View.OnClickListener onClickListener;
private AdminUnadoptedReposAdapter adapter;
private ActivityAdminCronTasksBinding binding;
private int PAGE = 1;
private int resultLimit;
private boolean reload = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityAdminCronTasksBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
viewModel = new ViewModelProvider(this).get(AdminUnadoptedReposViewModel.class);
resultLimit = Constants.getCurrentResultLimit(ctx);
initCloseListener();
binding.close.setOnClickListener(onClickListener);
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
binding.toolbarTitle.setText(R.string.unadoptedRepos);
binding.recyclerView.setHasFixedSize(true);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
binding.pullToRefresh.setOnRefreshListener(
() ->
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
binding.pullToRefresh.setRefreshing(false);
PAGE = 1;
binding.progressBar.setVisibility(View.VISIBLE);
reload = true;
viewModel.loadRepos(ctx, PAGE, resultLimit, null);
},
500));
adapter =
new AdminUnadoptedReposAdapter(
new ArrayList<>(),
() -> {
PAGE = 1;
binding.progressBar.setVisibility(View.VISIBLE);
reload = true;
viewModel.loadRepos(ctx, PAGE, resultLimit, null);
},
() -> {
PAGE += 1;
binding.progressBar.setVisibility(View.VISIBLE);
viewModel.loadRepos(ctx, PAGE, resultLimit, null);
},
binding);
binding.recyclerView.setAdapter(adapter);
fetchDataAsync(ctx);
}
private void fetchDataAsync(Context ctx) {
AtomicInteger prevSize = new AtomicInteger();
viewModel
.getUnadoptedRepos(ctx, PAGE, resultLimit, null)
.observe(
this,
list -> {
binding.progressBar.setVisibility(View.GONE);
boolean hasMore = reload || list.size() > prevSize.get();
reload = false;
prevSize.set(list.size());
if (list.size() > 0) {
adapter.updateList(list);
adapter.setHasMore(hasMore);
binding.noData.setVisibility(View.GONE);
} else {
binding.noData.setVisibility(View.VISIBLE);
}
});
}
private void initCloseListener() {
onClickListener = view -> finish();
}
}

View File

@@ -18,6 +18,7 @@ import org.apache.commons.lang3.StringUtils;
import org.gitnex.tea4j.v2.models.Cron;
import org.mian.gitnex.R;
import org.mian.gitnex.adapters.AdminCronTasksAdapter;
import org.mian.gitnex.adapters.AdminUnadoptedReposAdapter;
import org.mian.gitnex.api.models.settings.RepositoryGlobal;
import org.mian.gitnex.databinding.ActivityAdministrationBinding;
import org.mian.gitnex.databinding.BottomSheetGlobalRepositorySettingsBinding;
@@ -86,10 +87,7 @@ public class AdministrationActivity extends BaseActivity {
binding.cardCron.getRoot().setOnClickListener(v -> showCronTasksSheet());
binding.cardUnadopted
.getRoot()
.setOnClickListener(
v -> startActivity(new Intent(this, AdminUnadoptedReposActivity.class)));
binding.cardUnadopted.getRoot().setOnClickListener(v -> showUnadoptedReposSheet());
binding.cardRepoSettings.getRoot().setOnClickListener(v -> showRepositorySettings());
}
@@ -144,7 +142,7 @@ public class AdministrationActivity extends BaseActivity {
});
viewModel
.getIsLoading()
.getIsCronLoading()
.observe(
this,
loading ->
@@ -211,7 +209,7 @@ public class AdministrationActivity extends BaseActivity {
});
viewModel
.getIsLoading()
.getIsSettingsLoading()
.observe(
this,
loading -> {
@@ -248,6 +246,88 @@ public class AdministrationActivity extends BaseActivity {
}
}
private void showUnadoptedReposSheet() {
BottomsheetAdminCronTasksBinding sheetBinding =
BottomsheetAdminCronTasksBinding.inflate(getLayoutInflater());
BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(sheetBinding.getRoot());
AppUtil.applySheetStyle(dialog, true);
sheetBinding.title.setText(R.string.unadoptedRepos);
viewModel.resetUnadoptedPagination();
AdminUnadoptedReposAdapter adapter =
new AdminUnadoptedReposAdapter(new ArrayList<>(), this::showUnadoptedActionDialog);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
sheetBinding.recyclerView.setLayoutManager(layoutManager);
sheetBinding.recyclerView.setAdapter(adapter);
EndlessRecyclerViewScrollListener scrollListener =
new EndlessRecyclerViewScrollListener(layoutManager) {
@Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
viewModel.fetchUnadoptedRepos(
AdministrationActivity.this, page, resultLimit, false);
}
};
sheetBinding.recyclerView.addOnScrollListener(scrollListener);
viewModel
.getUnadoptedRepos()
.observe(
this,
list -> {
adapter.updateList(list);
sheetBinding
.layoutEmpty
.getRoot()
.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
});
viewModel
.getIsUnadoptedLoading()
.observe(
this,
loading ->
sheetBinding.expressiveLoader.setVisibility(
loading ? View.VISIBLE : View.GONE));
viewModel
.getRepoActionSuccess()
.observe(
this,
result -> {
if (result != null) {
String message;
if (result.isDelete()) {
message = getString(R.string.repoDeletionSuccess);
} else {
message = getString(R.string.repoAdopted, result.repoName());
}
Toasty.show(this, message);
}
});
viewModel.fetchUnadoptedRepos(this, 1, resultLimit, true);
dialog.show();
}
private void showUnadoptedActionDialog(String repoName) {
String[] parts = repoName.split("/");
new MaterialAlertDialogBuilder(this)
.setTitle(repoName)
.setMessage(getString(R.string.unadoptedReposMessage, parts[1], parts[0]))
.setNeutralButton(R.string.close, null)
.setPositiveButton(
R.string.menuDeleteText,
(d, w) -> viewModel.performRepoAction(this, repoName, true))
.setNegativeButton(
R.string.adoptRepo,
(d, w) -> viewModel.performRepoAction(this, repoName, false))
.show();
}
private void initCards() {
binding.cardUsers.cardIcon.setImageResource(R.drawable.ic_people);
binding.cardUsers.cardTitle.setText(R.string.adminUsers);

View File

@@ -1,169 +1,48 @@
package org.mian.gitnex.adapters;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.List;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.databinding.ActivityAdminCronTasksBinding;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.Toasty;
import retrofit2.Call;
import retrofit2.Callback;
import org.mian.gitnex.databinding.ListAdminUnadoptedReposBinding;
/**
* @author mmarif
* @author qwerty287
*/
public class AdminUnadoptedReposAdapter
extends RecyclerView.Adapter<AdminUnadoptedReposAdapter.UnadoptedViewHolder> {
extends RecyclerView.Adapter<AdminUnadoptedReposAdapter.ViewHolder> {
private final Runnable updateList;
private final Runnable loadMoreListener;
private final ActivityAdminCronTasksBinding activityAdminCronTasksBinding;
private List<String> repos;
private boolean isLoading = false, hasMore = true;
private final List<String> repos;
private final OnRepoClickListener listener;
public AdminUnadoptedReposAdapter(
List<String> list,
Runnable updateList,
Runnable loadMore,
ActivityAdminCronTasksBinding activityAdminCronTasksBinding) {
this.repos = list;
this.updateList = updateList;
this.loadMoreListener = loadMore;
this.activityAdminCronTasksBinding = activityAdminCronTasksBinding;
public interface OnRepoClickListener {
void onRepoClick(String repoName);
}
public AdminUnadoptedReposAdapter(List<String> repos, OnRepoClickListener listener) {
this.repos = repos;
this.listener = listener;
}
@NonNull @Override
public UnadoptedViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_admin_unadopted_repos, parent, false);
return new UnadoptedViewHolder(v);
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ListAdminUnadoptedReposBinding binding =
ListAdminUnadoptedReposBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull UnadoptedViewHolder holder, int position) {
if (position >= getItemCount() - 1 && hasMore && !isLoading && loadMoreListener != null) {
isLoading = true;
loadMoreListener.run();
}
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
String repo = repos.get(position);
String currentItem = repos.get(position);
holder.binding.repoName.setText(repo);
holder.binding.getRoot().setOnClickListener(v -> listener.onRepoClick(repo));
holder.repoName = currentItem;
holder.name.setText(currentItem);
}
private void updateAdapter(int position) {
repos.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, repos.size());
}
private void delete(final Context ctx, final String name) {
String[] repoSplit = name.split("/");
Call<Void> call =
RetrofitClient.getApiInterface(ctx)
.adminDeleteUnadoptedRepository(repoSplit[0], repoSplit[1]);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<Void> call, @NonNull retrofit2.Response<Void> response) {
switch (response.code()) {
case 204:
updateList.run();
Toasty.show(ctx, ctx.getString(R.string.repoDeletionSuccess));
break;
case 401:
AlertDialogs.authorizationTokenRevokedDialog(ctx);
break;
case 403:
Toasty.show(ctx, ctx.getString(R.string.authorizeError));
break;
case 404:
Toasty.show(ctx, ctx.getString(R.string.apiNotFound));
break;
default:
Toasty.show(ctx, ctx.getString(R.string.genericError));
}
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
Toasty.show(ctx, ctx.getString(R.string.genericServerResponseError));
}
});
}
private void adopt(final Context ctx, final String name, int position) {
String[] repoSplit = name.split("/");
Call<Void> call =
RetrofitClient.getApiInterface(ctx)
.adminAdoptRepository(repoSplit[0], repoSplit[1]);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<Void> call, @NonNull retrofit2.Response<Void> response) {
switch (response.code()) {
case 204:
updateAdapter(position);
if (getItemCount() == 0) {
activityAdminCronTasksBinding.noData.setVisibility(
View.VISIBLE);
}
Toasty.show(ctx, ctx.getString(R.string.repoAdopted, name));
break;
case 401:
AlertDialogs.authorizationTokenRevokedDialog(ctx);
break;
case 403:
Toasty.show(ctx, ctx.getString(R.string.authorizeError));
break;
case 404:
Toasty.show(ctx, ctx.getString(R.string.apiNotFound));
break;
default:
Toasty.show(ctx, ctx.getString(R.string.genericError));
}
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
Toasty.show(ctx, ctx.getString(R.string.genericServerResponseError));
}
});
holder.binding.getRoot().updateAppearance(position, getItemCount());
}
@Override
@@ -172,54 +51,18 @@ public class AdminUnadoptedReposAdapter
}
@SuppressLint("NotifyDataSetChanged")
public void updateList(List<String> list) {
this.repos = list;
public void updateList(List<String> newList) {
this.repos.clear();
this.repos.addAll(newList);
notifyDataSetChanged();
}
public void setHasMore(boolean hasMore) {
this.hasMore = hasMore;
isLoading = false;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
final ListAdminUnadoptedReposBinding binding;
public class UnadoptedViewHolder extends RecyclerView.ViewHolder {
private final TextView name;
private String repoName;
private UnadoptedViewHolder(View itemView) {
super(itemView);
Context ctx = itemView.getContext();
name = itemView.findViewById(R.id.repo_name);
itemView.setOnClickListener(
taskInfo -> {
String[] repoSplit = repoName.split("/");
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(repoName)
.setMessage(
ctx.getString(
R.string.unadoptedReposMessage,
repoSplit[1],
repoSplit[0]))
.setNeutralButton(R.string.close, null)
.setPositiveButton(
R.string.menuDeleteText,
((dialog, which) -> delete(ctx, repoName)))
.setNegativeButton(
R.string.adoptRepo,
((dialog, which) ->
adopt(
ctx,
repoName,
getBindingAdapterPosition())));
materialAlertDialogBuilder.create().show();
});
ViewHolder(ListAdminUnadoptedReposBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}

View File

@@ -1,78 +0,0 @@
package org.mian.gitnex.viewmodels;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.Toasty;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* @author mmarif
* @author qwerty287
*/
public class AdminUnadoptedReposViewModel extends ViewModel {
private MutableLiveData<List<String>> tasksList;
public LiveData<List<String>> getUnadoptedRepos(
Context ctx, int page, int limit, String query) {
tasksList = new MutableLiveData<>();
loadRepos(ctx, page, limit, query);
return tasksList;
}
public void loadRepos(final Context ctx, final int page, int limit, String query) {
Call<List<String>> call =
RetrofitClient.getApiInterface(ctx).adminUnadoptedList(page, limit, query);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<String>> call,
@NonNull Response<List<String>> response) {
if (response.isSuccessful()) {
if (page <= 1 || tasksList.getValue() == null) {
tasksList.postValue(response.body());
} else {
List<String> repos =
new ArrayList<>(
Objects.requireNonNull(tasksList.getValue()));
assert response.body() != null;
repos.addAll(response.body());
tasksList.postValue(repos);
}
} else if (response.code() == 401) {
AlertDialogs.authorizationTokenRevokedDialog(ctx);
} else if (response.code() == 403) {
Toasty.show(ctx, ctx.getString(R.string.authorizeError));
} else if (response.code() == 404) {
Toasty.show(ctx, ctx.getString(R.string.apiNotFound));
} else {
Toasty.show(ctx, ctx.getString(R.string.genericError));
}
}
@Override
public void onFailure(@NonNull Call<List<String>> call, @NonNull Throwable t) {
Toasty.show(ctx, ctx.getString(R.string.genericServerResponseError));
}
});
}
}

View File

@@ -23,12 +23,20 @@ public class AdministrationViewModel extends ViewModel {
private final MutableLiveData<RepositoryGlobal> repositorySettings = new MutableLiveData<>();
private final MutableLiveData<List<Cron>> cronTasks = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
private final MutableLiveData<List<String>> unadoptedRepos =
new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<Boolean> isSettingsLoading = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> isCronLoading = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> isUnadoptedLoading = new MutableLiveData<>(false);
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
private final MutableLiveData<String> taskSuccessMessage = new MutableLiveData<>();
private final MutableLiveData<RepoActionResult> repoActionSuccess = new MutableLiveData<>();
private int cronTotalCount = -1;
private boolean isCronLastPage = false;
private int unadoptedTotalCount = -1;
private boolean isUnadoptedLastPage = false;
public LiveData<RepositoryGlobal> getRepositorySettings() {
return repositorySettings;
@@ -38,8 +46,20 @@ public class AdministrationViewModel extends ViewModel {
return cronTasks;
}
public LiveData<Boolean> getIsLoading() {
return isLoading;
public LiveData<List<String>> getUnadoptedRepos() {
return unadoptedRepos;
}
public LiveData<Boolean> getIsSettingsLoading() {
return isSettingsLoading;
}
public LiveData<Boolean> getIsCronLoading() {
return isCronLoading;
}
public LiveData<Boolean> getIsUnadoptedLoading() {
return isUnadoptedLoading;
}
public LiveData<String> getErrorMessage() {
@@ -50,8 +70,12 @@ public class AdministrationViewModel extends ViewModel {
return taskSuccessMessage;
}
public LiveData<RepoActionResult> getRepoActionSuccess() {
return repoActionSuccess;
}
public void fetchRepositoryGlobalSettings(Context context) {
isLoading.setValue(true);
isSettingsLoading.setValue(true);
ApiRetrofitClient.getInstance(context)
.getRepositoryGlobalSettings()
.enqueue(
@@ -60,29 +84,26 @@ public class AdministrationViewModel extends ViewModel {
public void onResponse(
@NonNull Call<RepositoryGlobal> call,
@NonNull Response<RepositoryGlobal> response) {
isLoading.setValue(false);
if (response.isSuccessful()) {
isSettingsLoading.setValue(false);
if (response.isSuccessful())
repositorySettings.setValue(response.body());
} else {
errorMessage.setValue("Error: " + response.code());
}
else errorMessage.setValue("Error: " + response.code());
}
@Override
public void onFailure(
@NonNull Call<RepositoryGlobal> call, @NonNull Throwable t) {
isLoading.setValue(false);
isSettingsLoading.setValue(false);
errorMessage.setValue(t.getMessage());
}
});
}
public void fetchCronTasks(Context context, int page, int limit, boolean isRefresh) {
if (Boolean.TRUE.equals(isLoading.getValue())) return;
if (Boolean.TRUE.equals(isCronLoading.getValue())) return;
if (!isRefresh && isCronLastPage) return;
isLoading.setValue(true);
isCronLoading.setValue(true);
RetrofitClient.getApiInterface(context)
.adminCronList(page, limit)
.enqueue(
@@ -91,69 +112,155 @@ public class AdministrationViewModel extends ViewModel {
public void onResponse(
@NonNull Call<List<Cron>> call,
@NonNull Response<List<Cron>> response) {
isLoading.setValue(false);
isCronLoading.setValue(false);
if (response.isSuccessful() && response.body() != null) {
String totalHeader = response.headers().get("x-total-count");
if (totalHeader != null) {
cronTotalCount = Integer.parseInt(totalHeader);
}
List<Cron> currentList =
isRefresh
? new ArrayList<>()
: new ArrayList<>(
Objects.requireNonNull(
cronTasks.getValue()));
currentList.addAll(response.body());
cronTasks.setValue(currentList);
if (response.body().size() < limit
|| currentList.size() >= cronTotalCount) {
isCronLastPage = true;
}
} else {
errorMessage.setValue("Error: " + response.code());
}
handleCronResponse(
response.body(),
response.headers().get("x-total-count"),
limit,
isRefresh);
} else errorMessage.setValue("Error: " + response.code());
}
@Override
public void onFailure(
@NonNull Call<List<Cron>> call, @NonNull Throwable t) {
isLoading.setValue(false);
isCronLoading.setValue(false);
errorMessage.setValue(t.getMessage());
}
});
}
private void handleCronResponse(
List<Cron> body, String totalHeader, int limit, boolean isRefresh) {
if (totalHeader != null) cronTotalCount = Integer.parseInt(totalHeader);
List<Cron> currentList =
isRefresh
? new ArrayList<>()
: new ArrayList<>(Objects.requireNonNull(cronTasks.getValue()));
currentList.addAll(body);
cronTasks.setValue(currentList);
if (body.size() < limit || currentList.size() >= cronTotalCount) isCronLastPage = true;
}
public void runCronTask(Context context, String taskName) {
isCronLoading.setValue(true);
RetrofitClient.getApiInterface(context)
.adminCronRun(taskName)
.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<Void> call, @NonNull Response<Void> response) {
isCronLoading.setValue(false);
if (response.code() == 204) {
taskSuccessMessage.setValue(taskName);
taskSuccessMessage.setValue(null);
} else errorMessage.setValue("Error: " + response.code());
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
isCronLoading.setValue(false);
errorMessage.setValue(t.getMessage());
}
});
}
public void fetchUnadoptedRepos(Context context, int page, int limit, boolean isRefresh) {
if (Boolean.TRUE.equals(isUnadoptedLoading.getValue())) return;
if (!isRefresh && isUnadoptedLastPage) return;
isUnadoptedLoading.setValue(true);
RetrofitClient.getApiInterface(context)
.adminUnadoptedList(page, limit, null)
.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<String>> call,
@NonNull Response<List<String>> response) {
isUnadoptedLoading.setValue(false);
if (response.isSuccessful() && response.body() != null) {
handleUnadoptedResponse(
response.body(),
response.headers().get("x-total-count"),
limit,
isRefresh);
} else errorMessage.setValue("Error: " + response.code());
}
@Override
public void onFailure(
@NonNull Call<List<String>> call, @NonNull Throwable t) {
isUnadoptedLoading.setValue(false);
errorMessage.setValue(t.getMessage());
}
});
}
private void handleUnadoptedResponse(
List<String> body, String totalHeader, int limit, boolean isRefresh) {
if (totalHeader != null) unadoptedTotalCount = Integer.parseInt(totalHeader);
List<String> currentList =
isRefresh
? new ArrayList<>()
: new ArrayList<>(Objects.requireNonNull(unadoptedRepos.getValue()));
currentList.addAll(body);
unadoptedRepos.setValue(currentList);
if (body.size() < limit || currentList.size() >= unadoptedTotalCount)
isUnadoptedLastPage = true;
}
public void performRepoAction(Context context, String repoName, boolean isDelete) {
isUnadoptedLoading.setValue(true);
String[] parts = repoName.split("/");
Call<Void> call =
isDelete
? RetrofitClient.getApiInterface(context)
.adminDeleteUnadoptedRepository(parts[0], parts[1])
: RetrofitClient.getApiInterface(context)
.adminAdoptRepository(parts[0], parts[1]);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<Void> call, @NonNull Response<Void> response) {
isUnadoptedLoading.setValue(false);
if (response.code() == 204) {
List<String> current =
new ArrayList<>(
Objects.requireNonNull(unadoptedRepos.getValue()));
current.remove(repoName);
unadoptedRepos.setValue(current);
repoActionSuccess.setValue(new RepoActionResult(repoName, isDelete));
repoActionSuccess.setValue(null);
} else {
errorMessage.setValue("Error: " + response.code());
}
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
isUnadoptedLoading.setValue(false);
errorMessage.setValue(t.getMessage());
}
});
}
public void resetCronPagination() {
this.isCronLastPage = false;
this.cronTotalCount = -1;
this.cronTasks.setValue(new ArrayList<>());
}
public void runCronTask(Context context, String taskName) {
isLoading.setValue(true);
RetrofitClient.getApiInterface(context)
.adminCronRun(taskName)
.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<Void> call, @NonNull Response<Void> response) {
isLoading.setValue(false);
if (response.code() == 204) {
taskSuccessMessage.setValue(taskName);
taskSuccessMessage.setValue(null);
} else {
errorMessage.setValue("Error: " + response.code());
}
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
isLoading.setValue(false);
errorMessage.setValue(t.getMessage());
}
});
public void resetUnadoptedPagination() {
this.isUnadoptedLastPage = false;
this.unadoptedTotalCount = -1;
this.unadoptedRepos.setValue(new ArrayList<>());
}
public record RepoActionResult(String repoName, boolean isDelete) {}
}

View File

@@ -1,91 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/primaryBackgroundColor"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="@dimen/dimen0dp"
android:theme="@style/Widget.AppCompat.SearchView">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryBackgroundColor">
<ImageView
android:id="@+id/close"
android:layout_width="@dimen/dimen26dp"
android:layout_height="@dimen/dimen26dp"
android:layout_marginEnd="@dimen/dimen16dp"
android:layout_marginStart="@dimen/dimen16dp"
android:gravity="center_vertical"
android:contentDescription="@string/close"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:focusable="true"
android:clickable="true"
android:src="@drawable/ic_close" />
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/adminCron"
android:textColor="?attr/primaryTextColor"
android:maxLines="1"
android:textSize="@dimen/dimen20sp" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/primaryBackgroundColor"
android:layout_marginTop="@dimen/dimen56dp"
android:padding="@dimen/dimen8dp">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/pullToRefresh"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen56dp"
android:indeterminate="true"
style="@style/Widget.Material3.LinearProgressIndicator"
app:indicatorColor="?attr/progressIndicatorColor" />
<TextView
android:id="@+id/noData"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/dimen16dp"
android:gravity="center"
android:text="@string/noDataFound"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen20sp"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -23,14 +23,28 @@
<com.google.android.material.loadingindicator.LoadingIndicator
android:id="@+id/expressive_loader"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:layout_marginBottom="@dimen/dimen24dp"
android:layout_marginTop="@dimen/dimen48dp"
android:layout_marginBottom="@dimen/dimen48dp"
android:layout_gravity="center"
app:indicatorSize="@dimen/dimen48dp"
app:trackThickness="@dimen/dimen4dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<include
android:id="@+id/layout_empty"
layout="@layout/layout_empty_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen32dp"
android:layout_marginBottom="@dimen/dimen32dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<androidx.recyclerview.widget.RecyclerView

View File

@@ -1,35 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<com.google.android.material.listitem.ListItemLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingTop="@dimen/dimen4dp"
android:paddingBottom="@dimen/dimen4dp">
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
<com.google.android.material.listitem.ListItemCardView
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/materialCardViewElevatedStyle"
app:cardElevation="@dimen/dimen0dp">
android:checkable="false"
android:layout_marginVertical="@dimen/dimen1dp"
app:strokeWidth="0dp"
app:cardBackgroundColor="?attr/materialCardBackgroundColor">
<LinearLayout
<TextView
android:id="@+id/repo_name"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:background="?attr/materialCardBackgroundColor"
android:padding="@dimen/dimen12dp"
android:orientation="vertical">
android:padding="@dimen/dimen8dp"
android:textColor="?attr/primaryTextColor" />
<TextView
android:id="@+id/repo_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/dimen6dp"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"/>
</com.google.android.material.listitem.ListItemCardView>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</RelativeLayout>
</com.google.android.material.listitem.ListItemLayout>