Refactor instance administration - settings and cron tasks

This commit is contained in:
M M Arif
2026-03-25 18:06:47 +05:00
parent dc12b5fbc5
commit ea491bb322
15 changed files with 831 additions and 808 deletions

View File

@@ -144,9 +144,6 @@
android:name=".activities.CreatePullRequestActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activities.AdminCronTasksActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
<activity
android:name=".activities.WikiActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>

View File

@@ -1,88 +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 org.mian.gitnex.adapters.AdminCronTasksAdapter;
import org.mian.gitnex.databinding.ActivityAdminCronTasksBinding;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.viewmodels.AdminCronTasksViewModel;
/**
* @author mmarif
*/
public class AdminCronTasksActivity extends BaseActivity {
private AdminCronTasksViewModel adminCronTasksViewModel;
private View.OnClickListener onClickListener;
private AdminCronTasksAdapter adapter;
private ActivityAdminCronTasksBinding activityAdminCronTasksBinding;
private final int PAGE = 1;
private int resultLimit;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityAdminCronTasksBinding = ActivityAdminCronTasksBinding.inflate(getLayoutInflater());
setContentView(activityAdminCronTasksBinding.getRoot());
adminCronTasksViewModel = new ViewModelProvider(this).get(AdminCronTasksViewModel.class);
resultLimit = Constants.getCurrentResultLimit(ctx);
initCloseListener();
activityAdminCronTasksBinding.close.setOnClickListener(onClickListener);
Toolbar toolbar = activityAdminCronTasksBinding.toolbar;
setSupportActionBar(toolbar);
activityAdminCronTasksBinding.recyclerView.setHasFixedSize(true);
activityAdminCronTasksBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
activityAdminCronTasksBinding.pullToRefresh.setOnRefreshListener(
() ->
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
activityAdminCronTasksBinding.progressBar.setVisibility(
View.VISIBLE);
activityAdminCronTasksBinding.pullToRefresh
.setRefreshing(false);
adminCronTasksViewModel.loadCronTasksList(
ctx, PAGE, resultLimit);
},
500));
fetchDataAsync(ctx);
}
private void fetchDataAsync(Context ctx) {
adminCronTasksViewModel
.getCronTasksList(ctx, PAGE, resultLimit)
.observe(
this,
cronTasksListMain -> {
adapter = new AdminCronTasksAdapter(cronTasksListMain);
if (adapter.getItemCount() > 0) {
activityAdminCronTasksBinding.recyclerView.setAdapter(adapter);
activityAdminCronTasksBinding.noData.setVisibility(View.GONE);
activityAdminCronTasksBinding.progressBar.setVisibility(View.GONE);
} else {
activityAdminCronTasksBinding.noData.setVisibility(View.VISIBLE);
}
});
}
private void initCloseListener() {
onClickListener = view -> finish();
}
}

View File

@@ -1,17 +1,34 @@
package org.mian.gitnex.activities;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import androidx.annotation.NonNull;
import android.view.View;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import org.mian.gitnex.api.clients.ApiRetrofitClient;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Locale;
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.api.models.settings.RepositoryGlobal;
import org.mian.gitnex.databinding.ActivityAdministrationBinding;
import org.mian.gitnex.databinding.BottomSheetGlobalRepositorySettingsBinding;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import org.mian.gitnex.databinding.BottomsheetAdminCronTasksBinding;
import org.mian.gitnex.databinding.ItemAdministrationRepoSettingRowBinding;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.EndlessRecyclerViewScrollListener;
import org.mian.gitnex.helpers.TimeHelper;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.viewmodels.AdministrationViewModel;
/**
* @author mmarif
@@ -19,7 +36,8 @@ import retrofit2.Response;
public class AdministrationActivity extends BaseActivity {
private ActivityAdministrationBinding binding;
private BottomSheetDialog settingsBottomSheet;
private AdministrationViewModel viewModel;
private int resultLimit;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -27,80 +45,224 @@ public class AdministrationActivity extends BaseActivity {
binding = ActivityAdministrationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
resultLimit = Constants.getCurrentResultLimit(this);
viewModel = new ViewModelProvider(this).get(AdministrationViewModel.class);
initCards();
setupListeners();
handleIntentActions();
observeViewModel();
}
private void observeViewModel() {
viewModel
.getErrorMessage()
.observe(
this,
error -> {
if (error != null) Toasty.show(this, error);
});
viewModel
.getTaskSuccessMessage()
.observe(
this,
taskName -> {
if (taskName != null) {
Toasty.show(
this,
getString(R.string.adminCronTaskSuccessMsg, taskName));
}
});
}
private void setupListeners() {
binding.systemUsersFrame.setOnClickListener(
v -> startActivity(new Intent(this, AdminGetUsersActivity.class)));
binding.btnBack.setOnClickListener(v -> finish());
binding.adminCronFrame.setOnClickListener(
v -> startActivity(new Intent(this, AdminCronTasksActivity.class)));
binding.cardUsers
.getRoot()
.setOnClickListener(
v -> startActivity(new Intent(this, AdminGetUsersActivity.class)));
binding.unadoptedReposFrame.setOnClickListener(
v -> startActivity(new Intent(this, AdminUnadoptedReposActivity.class)));
binding.cardCron.getRoot().setOnClickListener(v -> showCronTasksSheet());
binding.adminRepositoryFrame.setOnClickListener(v -> showRepositorySettings());
binding.cardUnadopted
.getRoot()
.setOnClickListener(
v -> startActivity(new Intent(this, AdminUnadoptedReposActivity.class)));
binding.cardRepoSettings.getRoot().setOnClickListener(v -> showRepositorySettings());
}
private void handleIntentActions() {
String action = getIntent().getStringExtra("giteaAdminAction");
if (action != null) {
switch (action) {
case "users" -> startActivity(new Intent(this, AdminGetUsersActivity.class));
case "monitor" -> startActivity(new Intent(this, AdminCronTasksActivity.class));
}
}
private void showCronTasksSheet() {
BottomsheetAdminCronTasksBinding sheetBinding =
BottomsheetAdminCronTasksBinding.inflate(getLayoutInflater());
BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(sheetBinding.getRoot());
AppUtil.applySheetStyle(dialog, true);
viewModel.resetCronPagination();
AdminCronTasksAdapter adapter =
new AdminCronTasksAdapter(
new ArrayList<>(),
new AdminCronTasksAdapter.OnCronTaskListener() {
@Override
public void onRunTask(String taskName) {
viewModel.runCronTask(AdministrationActivity.this, taskName);
}
@Override
public void onShowDetails(Cron task) {
showCronDetailDialog(task);
}
});
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.fetchCronTasks(
AdministrationActivity.this, page, resultLimit, false);
}
};
sheetBinding.recyclerView.addOnScrollListener(scrollListener);
viewModel
.getCronTasks()
.observe(
this,
list -> {
if (list != null) {
adapter.updateList(list);
}
});
viewModel
.getIsLoading()
.observe(
this,
loading ->
sheetBinding.expressiveLoader.setVisibility(
loading ? View.VISIBLE : View.GONE));
viewModel.fetchCronTasks(this, 1, resultLimit, true);
dialog.show();
}
private void showCronDetailDialog(Cron task) {
View view = LayoutInflater.from(this).inflate(R.layout.layout_cron_task_info, null);
TextView taskScheduleContent = view.findViewById(R.id.taskScheduleContent);
TextView nextRunContent = view.findViewById(R.id.nextRunContent);
TextView lastRunContent = view.findViewById(R.id.lastRunContent);
TextView execTimeContent = view.findViewById(R.id.execTimeContent);
Locale locale = Locale.getDefault();
String nextRun =
(task.getNext() != null) ? TimeHelper.getFullDateTime(task.getNext(), locale) : "";
String lastRun =
(task.getPrev() != null) ? TimeHelper.getFullDateTime(task.getPrev(), locale) : "";
taskScheduleContent.setText(task.getSchedule());
nextRunContent.setText(nextRun);
lastRunContent.setText(lastRun);
execTimeContent.setText(String.valueOf(task.getExecTimes()));
new MaterialAlertDialogBuilder(this)
.setTitle(StringUtils.capitalize(task.getName().replace("_", " ")))
.setView(view)
.setPositiveButton(R.string.close, null)
.show();
}
private void showRepositorySettings() {
settingsBottomSheet = new BottomSheetDialog(this);
BottomSheetGlobalRepositorySettingsBinding sheetBinding =
BottomSheetGlobalRepositorySettingsBinding.inflate(LayoutInflater.from(this));
settingsBottomSheet.setContentView(sheetBinding.getRoot());
BottomSheetGlobalRepositorySettingsBinding.inflate(getLayoutInflater());
loadRepositorySettings(sheetBinding);
settingsBottomSheet.show();
}
BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(sheetBinding.getRoot());
AppUtil.applySheetStyle(dialog, false);
private void loadRepositorySettings(BottomSheetGlobalRepositorySettingsBinding sheetBinding) {
Call<RepositoryGlobal> call =
ApiRetrofitClient.getInstance(this).getRepositoryGlobalSettings();
sheetBinding.itemForks.settingLabel.setText(R.string.forks);
sheetBinding.itemMigrations.settingLabel.setText(R.string.migrations);
sheetBinding.itemHttpGit.settingLabel.setText(R.string.http_git);
sheetBinding.itemLfs.settingLabel.setText(R.string.lfs);
sheetBinding.itemMirrors.settingLabel.setText(R.string.mirrors);
sheetBinding.itemTime.settingLabel.setText(R.string.time_tracking);
sheetBinding.itemStars.settingLabel.setText(R.string.stars);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<RepositoryGlobal> call,
@NonNull Response<RepositoryGlobal> response) {
if (response.isSuccessful() && response.body() != null) {
updateSettingsUI(sheetBinding, response.body());
}
}
viewModel
.getRepositorySettings()
.observe(
this,
settings -> {
if (settings != null) {
updateSettingsUI(sheetBinding, settings);
sheetBinding.settingsContainer.setVisibility(View.VISIBLE);
}
});
@Override
public void onFailure(
@NonNull Call<RepositoryGlobal> call, @NonNull Throwable t) {
// Handle failure
}
});
viewModel
.getIsLoading()
.observe(
this,
loading -> {
sheetBinding.loadingIndicator.setVisibility(
loading ? View.VISIBLE : View.GONE);
if (loading) sheetBinding.settingsContainer.setVisibility(View.GONE);
});
viewModel.fetchRepositoryGlobalSettings(this);
dialog.show();
}
private void updateSettingsUI(
BottomSheetGlobalRepositorySettingsBinding sheetBinding, RepositoryGlobal settings) {
sheetBinding.forksDisabledValue.setText(
settings.isForksDisabled() ? "Disabled" : "Enabled");
sheetBinding.migrationsDisabledValue.setText(
settings.isMigrationsDisabled() ? "Disabled" : "Enabled");
sheetBinding.httpGitDisabledValue.setText(
settings.isHttpGitDisabled() ? "Disabled" : "Enabled");
sheetBinding.lfsDisabledValue.setText(settings.isLfsDisabled() ? "Disabled" : "Enabled");
sheetBinding.mirrorsDisabledValue.setText(
settings.isMirrorsDisabled() ? "Disabled" : "Enabled");
sheetBinding.timeTrackingDisabledValue.setText(
settings.isTimeTrackingDisabled() ? "Disabled" : "Enabled");
sheetBinding.starsDisabledValue.setText(
settings.isStarsDisabled() ? "Disabled" : "Enabled");
BottomSheetGlobalRepositorySettingsBinding binding, RepositoryGlobal settings) {
applyStatusStyle(binding.itemForks, !settings.isForksDisabled());
applyStatusStyle(binding.itemMigrations, !settings.isMigrationsDisabled());
applyStatusStyle(binding.itemHttpGit, !settings.isHttpGitDisabled());
applyStatusStyle(binding.itemLfs, !settings.isLfsDisabled());
applyStatusStyle(binding.itemMirrors, !settings.isMirrorsDisabled());
applyStatusStyle(binding.itemTime, !settings.isTimeTrackingDisabled());
applyStatusStyle(binding.itemStars, !settings.isStarsDisabled());
}
private void applyStatusStyle(
ItemAdministrationRepoSettingRowBinding itemBinding, boolean isEnabled) {
itemBinding.settingStatus.setText(isEnabled ? R.string.enabled : R.string.disabled);
if (isEnabled) {
itemBinding.statusContainer.setCardBackgroundColor(Color.parseColor("#1A2E7D32"));
itemBinding.settingStatus.setTextColor(ContextCompat.getColor(this, R.color.darkGreen));
} else {
itemBinding.statusContainer.setCardBackgroundColor(Color.parseColor("#1AC62828"));
itemBinding.settingStatus.setTextColor(ContextCompat.getColor(this, R.color.darkRed));
}
}
private void initCards() {
binding.cardUsers.cardIcon.setImageResource(R.drawable.ic_people);
binding.cardUsers.cardTitle.setText(R.string.adminUsers);
binding.cardUsers.cardSubtext.setText(R.string.adminUsersSubtext);
binding.cardCron.cardIcon.setImageResource(R.drawable.ic_tasks);
binding.cardCron.cardTitle.setText(R.string.adminCron);
binding.cardCron.cardSubtext.setText(R.string.adminCronSubtext);
binding.cardUnadopted.cardIcon.setImageResource(R.drawable.ic_directory_2);
binding.cardUnadopted.cardTitle.setText(R.string.unadoptedRepos);
binding.cardUnadopted.cardSubtext.setText(R.string.unadoptedReposSubtext);
binding.cardRepoSettings.cardIcon.setImageResource(R.drawable.ic_repo);
binding.cardRepoSettings.cardTitle.setText(R.string.repoSettingsTitle);
binding.cardRepoSettings.cardSubtext.setText(R.string.repoSettingsSubtext);
}
}

View File

@@ -50,7 +50,9 @@ public class AccountSettingsSSHKeysAdapter
holder.binding.copyFrame.setOnClickListener(
v -> {
AppUtil.copyToClipboard(
context, currentKey.getKey(), context.getString(R.string.copied_to_clipboard));
context,
currentKey.getKey(),
context.getString(R.string.copied_to_clipboard));
});
holder.binding.deleteFrame.setOnClickListener(

View File

@@ -1,160 +1,72 @@
package org.mian.gitnex.adapters;
import android.content.Context;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
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 java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.gitnex.tea4j.v2.models.Cron;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.TimeHelper;
import org.mian.gitnex.helpers.Toasty;
import retrofit2.Call;
import retrofit2.Callback;
import org.mian.gitnex.databinding.ListAdminCronTasksBinding;
/**
* @author mmarif
*/
public class AdminCronTasksAdapter
extends RecyclerView.Adapter<AdminCronTasksAdapter.CronTasksViewHolder> {
public class AdminCronTasksAdapter extends RecyclerView.Adapter<AdminCronTasksAdapter.ViewHolder> {
private final List<Cron> tasksList;
private final List<Cron> tasks;
private final OnCronTaskListener listener;
public static class CronTasksViewHolder extends RecyclerView.ViewHolder {
public interface OnCronTaskListener {
void onRunTask(String taskName);
private Cron cronTasks;
private final TextView taskName;
private CronTasksViewHolder(View itemView) {
super(itemView);
Context ctx = itemView.getContext();
final Locale locale = ctx.getResources().getConfiguration().locale;
ImageView runTask = itemView.findViewById(R.id.runTask);
taskName = itemView.findViewById(R.id.taskName);
taskName.setOnClickListener(
taskInfo -> {
String nextRun = "";
String lastRun = "";
if (cronTasks.getNext() != null) {
nextRun = TimeHelper.formatTime(cronTasks.getNext(), locale);
}
if (cronTasks.getPrev() != null) {
lastRun = TimeHelper.formatTime(cronTasks.getPrev(), locale);
}
View view =
LayoutInflater.from(ctx)
.inflate(R.layout.layout_cron_task_info, null);
TextView taskScheduleContent = view.findViewById(R.id.taskScheduleContent);
TextView nextRunContent = view.findViewById(R.id.nextRunContent);
TextView lastRunContent = view.findViewById(R.id.lastRunContent);
TextView execTimeContent = view.findViewById(R.id.execTimeContent);
taskScheduleContent.setText(cronTasks.getSchedule());
nextRunContent.setText(nextRun);
lastRunContent.setText(lastRun);
execTimeContent.setText(String.valueOf(cronTasks.getExecTimes()));
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx)
.setTitle(
StringUtils.capitalize(
cronTasks.getName().replace("_", " ")))
.setView(view)
.setNeutralButton(ctx.getString(R.string.close), null);
materialAlertDialogBuilder.create().show();
});
runTask.setOnClickListener(taskInfo -> runCronTask(ctx, cronTasks.getName()));
}
void onShowDetails(Cron task);
}
public AdminCronTasksAdapter(List<Cron> tasksListMain) {
this.tasksList = tasksListMain;
@SuppressLint("NotifyDataSetChanged")
public void updateList(List<Cron> newList) {
this.tasks.clear();
this.tasks.addAll(newList);
notifyDataSetChanged();
}
public AdminCronTasksAdapter(List<Cron> tasks, OnCronTaskListener listener) {
this.tasks = tasks;
this.listener = listener;
}
@NonNull @Override
public AdminCronTasksAdapter.CronTasksViewHolder onCreateViewHolder(
@NonNull ViewGroup parent, int viewType) {
View v =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_admin_cron_tasks, parent, false);
return new AdminCronTasksAdapter.CronTasksViewHolder(v);
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ListAdminCronTasksBinding binding =
ListAdminCronTasksBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(
@NonNull AdminCronTasksAdapter.CronTasksViewHolder holder, int position) {
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Cron task = tasks.get(position);
String cleanName = StringUtils.capitalize(task.getName().replace("_", " "));
Cron currentItem = tasksList.get(position);
holder.binding.taskName.setText(cleanName);
holder.binding.runTask.setOnClickListener(v -> listener.onRunTask(task.getName()));
holder.binding.getRoot().setOnClickListener(v -> listener.onShowDetails(task));
holder.cronTasks = currentItem;
holder.taskName.setText(StringUtils.capitalize(currentItem.getName().replace("_", " ")));
}
private static void runCronTask(final Context ctx, final String taskName) {
Call<Void> call = RetrofitClient.getApiInterface(ctx).adminCronRun(taskName);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<Void> call, @NonNull retrofit2.Response<Void> response) {
switch (response.code()) {
case 204:
Toasty.show(
ctx,
ctx.getString(R.string.adminCronTaskSuccessMsg, taskName));
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
public int getItemCount() {
return tasksList.size();
return tasks.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ListAdminCronTasksBinding binding;
ViewHolder(ListAdminCronTasksBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}

View File

@@ -1,65 +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.List;
import org.gitnex.tea4j.v2.models.Cron;
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
*/
public class AdminCronTasksViewModel extends ViewModel {
private MutableLiveData<List<Cron>> tasksList;
public LiveData<List<Cron>> getCronTasksList(Context ctx, int page, int limit) {
tasksList = new MutableLiveData<>();
loadCronTasksList(ctx, page, limit);
return tasksList;
}
public void loadCronTasksList(final Context ctx, int page, int limit) {
Call<List<Cron>> call = RetrofitClient.getApiInterface(ctx).adminCronList(page, limit);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<Cron>> call,
@NonNull Response<List<Cron>> response) {
if (response.isSuccessful()) {
tasksList.postValue(response.body());
} 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<Cron>> call, @NonNull Throwable t) {
Toasty.show(ctx, ctx.getString(R.string.genericServerResponseError));
}
});
}
}

View File

@@ -0,0 +1,159 @@
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.gitnex.tea4j.v2.models.Cron;
import org.mian.gitnex.api.clients.ApiRetrofitClient;
import org.mian.gitnex.api.models.settings.RepositoryGlobal;
import org.mian.gitnex.clients.RetrofitClient;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* @author mmarif
*/
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<String> errorMessage = new MutableLiveData<>();
private final MutableLiveData<String> taskSuccessMessage = new MutableLiveData<>();
private int cronTotalCount = -1;
private boolean isCronLastPage = false;
public LiveData<RepositoryGlobal> getRepositorySettings() {
return repositorySettings;
}
public LiveData<List<Cron>> getCronTasks() {
return cronTasks;
}
public LiveData<Boolean> getIsLoading() {
return isLoading;
}
public LiveData<String> getErrorMessage() {
return errorMessage;
}
public LiveData<String> getTaskSuccessMessage() {
return taskSuccessMessage;
}
public void fetchRepositoryGlobalSettings(Context context) {
isLoading.setValue(true);
ApiRetrofitClient.getInstance(context)
.getRepositoryGlobalSettings()
.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<RepositoryGlobal> call,
@NonNull Response<RepositoryGlobal> response) {
isLoading.setValue(false);
if (response.isSuccessful()) {
repositorySettings.setValue(response.body());
} else {
errorMessage.setValue("Error: " + response.code());
}
}
@Override
public void onFailure(
@NonNull Call<RepositoryGlobal> call, @NonNull Throwable t) {
isLoading.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 (!isRefresh && isCronLastPage) return;
isLoading.setValue(true);
RetrofitClient.getApiInterface(context)
.adminCronList(page, limit)
.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<Cron>> call,
@NonNull Response<List<Cron>> response) {
isLoading.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());
}
}
@Override
public void onFailure(
@NonNull Call<List<Cron>> call, @NonNull Throwable t) {
isLoading.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());
}
});
}
}

View File

@@ -4,149 +4,121 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/dimen56dp"
android:background="?attr/primaryBackgroundColor">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="@dimen/dimen56dp"
android:paddingBottom="@dimen/dimen84dp">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="@dimen/dimen8dp">
android:paddingHorizontal="@dimen/dimen16dp">
<LinearLayout
android:id="@+id/systemUsersFrame"
android:layout_width="match_parent"
<include
android:id="@+id/cardUsers"
layout="@layout/item_card_full_generic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/dimen16dp">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_people"
android:contentDescription="@string/generalImgContentText"
app:tint="?attr/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="@dimen/dimen16dp"
android:text="@string/adminUsers"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_chevron_right"
android:contentDescription="@string/generalImgContentText"
app:tint="?attr/colorPrimary" />
</LinearLayout>
<LinearLayout
android:id="@+id/adminCronFrame"
android:layout_width="match_parent"
<include
android:id="@+id/cardCron"
layout="@layout/item_card_full_generic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/dimen16dp">
android:layout_marginTop="@dimen/dimen12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cardUsers" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_tasks"
android:contentDescription="@string/generalImgContentText"
app:tint="?attr/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="@dimen/dimen16dp"
android:text="@string/adminCron"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_chevron_right"
android:contentDescription="@string/generalImgContentText"
app:tint="?attr/colorPrimary" />
</LinearLayout>
<LinearLayout
android:id="@+id/unadoptedReposFrame"
android:layout_width="match_parent"
<include
android:id="@+id/cardUnadopted"
layout="@layout/item_card_full_generic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/dimen16dp">
android:layout_marginTop="@dimen/dimen12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cardCron" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_directory_2"
android:contentDescription="@string/generalImgContentText"
app:tint="?attr/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="@dimen/dimen16dp"
android:text="@string/unadoptedRepos"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/generalImgContentText"
app:srcCompat="@drawable/ic_chevron_right"
app:tint="?attr/colorPrimary" />
</LinearLayout>
<LinearLayout
android:id="@+id/admin_repository_frame"
android:layout_width="match_parent"
<include
android:id="@+id/cardRepoSettings"
layout="@layout/item_card_full_generic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/dimen16dp">
android:layout_marginTop="@dimen/dimen12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cardUnadopted" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_repo"
android:contentDescription="@string/generalImgContentText"
app:tint="?attr/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="@dimen/dimen16dp"
android:text="@string/repoSettingsTitle"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.dockedtoolbar.DockedToolbarLayout
android:id="@+id/docked_toolbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:paddingBottomSystemWindowInsets="false"
app:paddingLeftSystemWindowInsets="false"
app:paddingRightSystemWindowInsets="false"
app:paddingTopSystemWindowInsets="false"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="@dimen/dimen32dp"
app:shapeAppearanceOverlay="@style/ShapeAppearance.RoundedDock"
android:backgroundTint="?attr/colorSurfaceContainerHigh"
app:layout_behavior="com.google.android.material.behavior.HideViewOnScrollBehavior">
<com.google.android.material.overflow.OverflowLinearLayout
android:id="@+id/docked_toolbar_child"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_back"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_arrow_back"
app:iconGravity="textStart"
app:iconPadding="@dimen/dimen0dp"
android:layout_gravity="center_horizontal"
android:backgroundTint="@android:color/transparent"
android:background="?attr/selectableItemBackgroundBorderless"
android:insetLeft="@dimen/dimen0dp"
android:insetRight="@dimen/dimen0dp"
android:contentDescription="@string/close" />
<View
android:layout_width="@dimen/dimen1dp"
android:layout_height="@dimen/dimen24dp"
android:layout_marginHorizontal="@dimen/dimen8dp"
android:layout_gravity="center_vertical|center_horizontal"
android:background="?attr/colorOutlineVariant" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/pageTitleAdministration"
android:layout_gravity="center_vertical|center_horizontal"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:textColor="?attr/primaryTextColor"
android:layout_marginStart="@dimen/dimen8dp"
android:layout_marginEnd="@dimen/dimen8dp" />
</com.google.android.material.overflow.OverflowLinearLayout>
</com.google.android.material.dockedtoolbar.DockedToolbarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,243 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryBackgroundColor"
android:orientation="vertical"
android:padding="@dimen/dimen16dp">
android:paddingHorizontal="@dimen/dimen24dp"
android:paddingBottom="@dimen/dimen32dp">
<View
android:layout_width="@dimen/dimen32dp"
android:layout_height="@dimen/dimen6dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/dimen8dp"
android:background="@drawable/bottom_sheet_handle"
android:backgroundTint="?attr/primaryTextColor" />
<LinearLayout
android:id="@+id/milestonesFilterHeadFrame"
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/drag_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/headerTitle"
style="@style/TextAppearance.Material3.HeadlineSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/repoSettingsTitle"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/drag_handle" />
<com.google.android.material.loadingindicator.LoadingIndicator
android:id="@+id/loadingIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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/headerTitle"
android:layout_marginTop="@dimen/dimen48dp"
android:layout_marginBottom="@dimen/dimen48dp"/>
<LinearLayout
android:id="@+id/settingsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen16dp"
android:orientation="vertical"
android:padding="@dimen/dimen8dp">
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/headerTitle">
<TextView
android:id="@+id/bottomSheetHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/repoSettingsTitle"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen18sp"/>
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="@dimen/dimen28dp"
android:layout_height="@dimen/dimen4dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen16dp"
app:cardCornerRadius="@dimen/dimen24dp"
app:cardElevation="@dimen/dimen0dp">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/fabColor" />
</com.google.android.material.card.MaterialCardView>
<include android:id="@+id/itemForks" layout="@layout/item_administration_repo_setting_row" />
<include android:id="@+id/itemMigrations" layout="@layout/item_administration_repo_setting_row" />
<include android:id="@+id/itemHttpGit" layout="@layout/item_administration_repo_setting_row" />
<include android:id="@+id/itemLfs" layout="@layout/item_administration_repo_setting_row" />
<include android:id="@+id/itemMirrors" layout="@layout/item_administration_repo_setting_row" />
<include android:id="@+id/itemTime" layout="@layout/item_administration_repo_setting_row" />
<include android:id="@+id/itemStars" layout="@layout/item_administration_repo_setting_row" />
</LinearLayout>
<!-- Forks -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="@dimen/dimen12dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/forks"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<TextView
android:id="@+id/forksDisabledValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Migrations -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="@dimen/dimen12dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/migrations"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<TextView
android:id="@+id/migrationsDisabledValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- HTTP Git -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="@dimen/dimen12dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/http_git"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<TextView
android:id="@+id/httpGitDisabledValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- LFS -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="@dimen/dimen12dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/lfs"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<TextView
android:id="@+id/lfsDisabledValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Mirrors -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="@dimen/dimen12dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/mirrors"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<TextView
android:id="@+id/mirrorsDisabledValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Time Tracking -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="@dimen/dimen12dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/time_tracking"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<TextView
android:id="@+id/timeTrackingDisabledValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Stars -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="@dimen/dimen12dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/stars"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
<TextView
android:id="@+id/starsDisabledValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/dimen24dp">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/drag_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Material3.HeadlineSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/adminCron"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/drag_handle" />
<com.google.android.material.loadingindicator.LoadingIndicator
android:id="@+id/expressive_loader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen24dp"
android:layout_marginBottom="@dimen/dimen24dp"
android:layout_gravity="center"
app:indicatorSize="@dimen/dimen48dp"
app:trackThickness="@dimen/dimen4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/title" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen16dp"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingBottom="@dimen/dimen32dp"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="@dimen/dimen12dp">
<TextView
android:id="@+id/settingLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColor="?attr/primaryTextColor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/statusContainer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/statusContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/dimen24dp"
app:cardElevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:strokeWidth="0dp">
<TextView
android:id="@+id/settingStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/dimen16dp"
android:paddingVertical="@dimen/dimen6dp"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:textStyle="bold"
android:textAllCaps="true" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="?attr/materialCardBackgroundColor"
app:cardCornerRadius="@dimen/dimen32dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen24dp">
<ImageView
android:id="@+id/cardIcon"
android:layout_width="@dimen/dimen24dp"
android:layout_height="@dimen/dimen24dp"
android:contentDescription="@string/generalImgContentText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?attr/iconsColor" />
<TextView
android:id="@+id/cardTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dimen12dp"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall.Emphasized"
app:layout_constraintBottom_toTopOf="@+id/cardSubtext"
app:layout_constraintEnd_toStartOf="@+id/cardIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/cardSubtext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen4dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?android:attr/textColorSecondary"
android:alpha=".7"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/cardTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cardTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -1,97 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="25dp">
android:padding="@dimen/dimen24dp">
<LinearLayout
<TextView
android:id="@+id/taskScheduleHeader"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0.7"
android:text="@string/adminCronScheduleHeader"
app:layout_constraintEnd_toStartOf="@+id/execTimeHeader"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/taskScheduleContent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen4dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/taskScheduleHeader"
app:layout_constraintStart_toStartOf="@+id/taskScheduleHeader"
app:layout_constraintTop_toBottomOf="@id/taskScheduleHeader" />
<TextView
android:id="@+id/execTimeHeader"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0.7"
android:text="@string/adminCronExecutionHeader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/taskScheduleHeader"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/execTimeContent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen4dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/execTimeHeader"
app:layout_constraintStart_toStartOf="@+id/execTimeHeader"
app:layout_constraintTop_toBottomOf="@id/execTimeHeader" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen16dp"
app:layout_constraintTop_toBottomOf="@id/taskScheduleContent" />
<TextView
android:id="@+id/taskScheduleHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/adminCronScheduleHeader"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"/>
<TextView
android:id="@+id/taskScheduleContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/primaryTextColor"/>
</LinearLayout>
<LinearLayout
<TextView
android:id="@+id/nextRunHeader"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:orientation="vertical">
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen16dp"
android:alpha="0.7"
android:text="@string/adminCronNextRunHeader"
app:layout_constraintTop_toBottomOf="@id/divider" />
<TextView
android:id="@+id/nextRunHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/adminCronNextRunHeader"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"/>
<TextView
android:id="@+id/nextRunContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/primaryTextColor"/>
</LinearLayout>
<LinearLayout
<TextView
android:id="@+id/nextRunContent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:orientation="vertical">
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen4dp"
android:textColor="?attr/colorPrimary"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/nextRunHeader" />
<TextView
android:id="@+id/lastRunHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/adminCronLastRunHeader"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"/>
<TextView
android:id="@+id/lastRunContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/primaryTextColor"/>
</LinearLayout>
<LinearLayout
<TextView
android:id="@+id/lastRunHeader"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:orientation="vertical">
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen16dp"
android:alpha="0.7"
android:text="@string/adminCronLastRunHeader"
app:layout_constraintTop_toBottomOf="@id/nextRunContent" />
<TextView
android:id="@+id/execTimeHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/adminCronExecutionHeader"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"/>
<TextView
android:id="@+id/lastRunContent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen4dp"
app:layout_constraintTop_toBottomOf="@id/lastRunHeader" />
<TextView
android:id="@+id/execTimeContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:textColor="?attr/primaryTextColor"
android:textColorLink="@color/lightBlue"/>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,46 +1,52 @@
<?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">
<RelativeLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:background="?attr/materialCardBackgroundColor"
android:orientation="horizontal"
android:padding="@dimen/dimen12dp">
android:padding="@dimen/dimen8dp">
<TextView
android:id="@+id/taskName"
android:layout_width="match_parent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/dimen6dp"
android:layout_marginEnd="@dimen/dimen16dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/adminCron"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen16sp" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/runTask"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/runTask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_alignParentEnd="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:scaleType="fitCenter"
android:contentDescription="@string/adminCron"
android:src="@drawable/ic_play" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.listitem.ListItemCardView>
</RelativeLayout>
</com.google.android.material.listitem.ListItemLayout>

View File

@@ -616,6 +616,8 @@
<string name="template">Template</string>
<string name="accounts">Accounts</string>
<string name="copied">Copied</string>
<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>
<!-- generic copy -->
<string name="exploreUsers">Explore users</string>
@@ -1098,4 +1100,8 @@
<string name="forked_repository">forked repository</string>
<string name="mirrored_repository">mirrored repository</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="adminUsersSubtext">Manage all system accounts and users</string>
<string name="adminCronSubtext">View and execute system scheduled maintenance tasks</string>
<string name="unadoptedReposSubtext">Search and claim repositories found on disk</string>
<string name="repoSettingsSubtext">View global repository and LFS settings</string>
</resources>