Tracked time (issues/prs) and created by me filter for My Issues (#1421) All checks were successful ci/woodpecker/push/locale Pipeline was successful ci/woodpecker/push/build Pipeline was successful ci/woodpecker/push/check Pipeline was successful ci/woodpecker/push/finish Pipeline was successful ci/woodpecker/cron/check Pipeline was successful ci/woodpecker/cron/build Pipeline was successful ci/woodpecker/cron/locale Pipeline was successful
All checks were successful
ci/woodpecker/push/locale Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/check Pipeline was successful
ci/woodpecker/push/finish Pipeline was successful
ci/woodpecker/cron/check Pipeline was successful
ci/woodpecker/cron/build Pipeline was successful
ci/woodpecker/cron/locale Pipeline was successful
Closes #247 Reviewed-on: #1421 Co-authored-by: M M Arif <mmarif@swatian.com> Co-committed-by: M M Arif <mmarif@swatian.com>
This commit is contained in:
parent bd989dd166
commit 186ed2bb70
14 changed files with 894 additions and 210 deletions
| @ -70,7 +70,8 @@ import retrofit2.Callback; | |||
*/ | ||||
@SuppressWarnings("ConstantConditions") | ||||
public class MainActivity extends BaseActivity | ||||
implements NavigationView.OnNavigationItemSelectedListener, BottomSheetListener { | ||||
implements NavigationView.OnNavigationItemSelectedListener, | ||||
BottomSheetMyIssuesFilterFragment.BottomSheetListener { | ||||
| ||||
public static boolean refActivity = false; | ||||
public static boolean reloadRepos = false; | ||||
| @ -323,15 +324,14 @@ public class MainActivity extends BaseActivity | |||
| ||||
mainIntent.removeExtra("launchFragment"); | ||||
| ||||
switch (launchFragment) { | ||||
case "notifications": | ||||
toolbarTitle.setText(getResources().getString(R.string.pageTitleNotifications)); | ||||
getSupportFragmentManager() | ||||
.beginTransaction() | ||||
.replace(R.id.fragment_container, new NotificationsFragment()) | ||||
.commit(); | ||||
navigationView.setCheckedItem(R.id.nav_notifications); | ||||
return; | ||||
if (launchFragment.equals("notifications")) { | ||||
toolbarTitle.setText(getResources().getString(R.string.pageTitleNotifications)); | ||||
getSupportFragmentManager() | ||||
.beginTransaction() | ||||
.replace(R.id.fragment_container, new NotificationsFragment()) | ||||
.commit(); | ||||
navigationView.setCheckedItem(R.id.nav_notifications); | ||||
return; | ||||
} | ||||
} | ||||
| ||||
| @ -590,22 +590,12 @@ public class MainActivity extends BaseActivity | |||
@Override | ||||
public void onButtonClicked(String text) { | ||||
| ||||
switch (text) { | ||||
case "openMyIssues": | ||||
if (getFragmentRefreshListener() != null) { | ||||
getFragmentRefreshListener().onRefresh("open"); | ||||
} | ||||
break; | ||||
case "closedMyIssues": | ||||
if (getFragmentRefreshListener() != null) { | ||||
getFragmentRefreshListener().onRefresh("closed"); | ||||
} | ||||
break; | ||||
case "assignedToMe": | ||||
if (getFragmentRefreshListener() != null) { | ||||
getFragmentRefreshListener().onRefresh("assignedToMe"); | ||||
} | ||||
break; | ||||
String[] parts = text.split("_"); | ||||
String state = parts[0]; | ||||
String filter = parts[1]; | ||||
| ||||
if (getFragmentRefreshListener() != null) { | ||||
getFragmentRefreshListener().onRefresh(state + "_" + filter); | ||||
} | ||||
} | ||||
| ||||
| @ -731,8 +721,16 @@ public class MainActivity extends BaseActivity | |||
| ||||
if (id == R.id.filter) { | ||||
| ||||
Fragment currentFragment = | ||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container); | ||||
String currentFilter = "open_created_by_me"; | ||||
| ||||
if (currentFragment instanceof MyIssuesFragment) { | ||||
currentFilter = ((MyIssuesFragment) currentFragment).getCurrentFilter(); | ||||
} | ||||
| ||||
BottomSheetMyIssuesFilterFragment filterBottomSheet = | ||||
new BottomSheetMyIssuesFilterFragment(); | ||||
BottomSheetMyIssuesFilterFragment.newInstance(currentFilter); | ||||
filterBottomSheet.show(getSupportFragmentManager(), "myIssuesFilterMenuBottomSheet"); | ||||
return true; | ||||
} | ||||
| @ -961,13 +959,15 @@ public class MainActivity extends BaseActivity | |||
this.profileInitListener = profileInitListener; | ||||
} | ||||
| ||||
// My issues interface | ||||
public FragmentRefreshListener getFragmentRefreshListener() { | ||||
return fragmentRefreshListenerMyIssues; | ||||
} | ||||
| ||||
public void setFragmentRefreshListenerMyIssues( | ||||
FragmentRefreshListener fragmentRefreshListener) { | ||||
this.fragmentRefreshListenerMyIssues = fragmentRefreshListener; | ||||
public void setFragmentRefreshListenerMyIssues(FragmentRefreshListener listener) { | ||||
this.fragmentRefreshListenerMyIssues = listener; | ||||
} | ||||
| ||||
public interface FragmentRefreshListener { | ||||
void onRefresh(String myIssues); | ||||
} | ||||
} | ||||
| |
| @ -0,0 +1,87 @@ | |||
package org.mian.gitnex.adapters; | ||||
| ||||
import android.view.LayoutInflater; | ||||
import android.view.ViewGroup; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.recyclerview.widget.RecyclerView; | ||||
import com.bumptech.glide.Glide; | ||||
import java.util.List; | ||||
import java.util.Locale; | ||||
import org.gitnex.tea4j.v2.models.TrackedTime; | ||||
import org.mian.gitnex.R; | ||||
import org.mian.gitnex.databinding.ListTrackedTimeBinding; | ||||
| ||||
/** | ||||
* @author mmarif | ||||
*/ | ||||
public class TrackedTimeAdapter | ||||
extends RecyclerView.Adapter<TrackedTimeAdapter.TrackedTimeViewHolder> { | ||||
| ||||
private final List<TrackedTime> trackedTimeList; | ||||
private OnDeleteClickListener deleteClickListener; | ||||
| ||||
public TrackedTimeAdapter(List<TrackedTime> trackedTimeList) { | ||||
this.trackedTimeList = trackedTimeList; | ||||
} | ||||
| ||||
public void setOnDeleteClickListener(OnDeleteClickListener listener) { | ||||
this.deleteClickListener = listener; | ||||
} | ||||
| ||||
@NonNull @Override | ||||
public TrackedTimeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||
ListTrackedTimeBinding binding = | ||||
ListTrackedTimeBinding.inflate( | ||||
LayoutInflater.from(parent.getContext()), parent, false); | ||||
return new TrackedTimeViewHolder(binding); | ||||
} | ||||
| ||||
@Override | ||||
public void onBindViewHolder(@NonNull TrackedTimeViewHolder holder, int position) { | ||||
TrackedTime time = trackedTimeList.get(position); | ||||
| ||||
Glide.with(holder.itemView.getContext()) | ||||
.load(time.getIssue().getUser().getAvatarUrl()) | ||||
.placeholder(R.drawable.ic_person) | ||||
.into(holder.binding.userAvatar); | ||||
| ||||
String userName = | ||||
time.getIssue().getUser().getFullName().isEmpty() | ||||
? time.getIssue().getUser().getLogin() | ||||
: time.getIssue().getUser().getFullName(); | ||||
holder.binding.userName.setText(userName); | ||||
| ||||
long totalSeconds = time.getTime(); | ||||
long hours = totalSeconds / 3600; | ||||
long minutes = (totalSeconds % 3600) / 60; | ||||
long seconds = totalSeconds % 60; | ||||
holder.binding.trackedTimeEntry.setText( | ||||
String.format(Locale.US, "%dh %dm %ds", hours, minutes, seconds)); | ||||
| ||||
holder.binding.deleteTrackedTime.setEnabled(true); | ||||
holder.binding.deleteTrackedTime.setOnClickListener( | ||||
v -> { | ||||
if (deleteClickListener != null) { | ||||
deleteClickListener.onDeleteClick(time, position); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
@Override | ||||
public int getItemCount() { | ||||
return trackedTimeList.size(); | ||||
} | ||||
| ||||
public static class TrackedTimeViewHolder extends RecyclerView.ViewHolder { | ||||
private final ListTrackedTimeBinding binding; | ||||
| ||||
TrackedTimeViewHolder(ListTrackedTimeBinding binding) { | ||||
super(binding.getRoot()); | ||||
this.binding = binding; | ||||
} | ||||
} | ||||
| ||||
public interface OnDeleteClickListener { | ||||
void onDeleteClick(TrackedTime time, int position); | ||||
} | ||||
} |
| @ -9,7 +9,6 @@ import androidx.annotation.NonNull; | |||
import androidx.annotation.Nullable; | ||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; | ||||
import org.mian.gitnex.databinding.BottomSheetMyIssuesFilterBinding; | ||||
import org.mian.gitnex.structs.BottomSheetListener; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
| @ -17,6 +16,32 @@ import org.mian.gitnex.structs.BottomSheetListener; | |||
public class BottomSheetMyIssuesFilterFragment extends BottomSheetDialogFragment { | ||||
| ||||
private BottomSheetListener bmListener; | ||||
private String selectedState = "open"; | ||||
private String selectedFilter = "created_by_me"; | ||||
private static final String ARG_FILTER = "currentFilter"; | ||||
| ||||
public static BottomSheetMyIssuesFilterFragment newInstance(String currentFilter) { | ||||
| ||||
BottomSheetMyIssuesFilterFragment fragment = new BottomSheetMyIssuesFilterFragment(); | ||||
Bundle args = new Bundle(); | ||||
args.putString(ARG_FILTER, currentFilter); | ||||
fragment.setArguments(args); | ||||
return fragment; | ||||
} | ||||
| ||||
@Override | ||||
public void onCreate(@Nullable Bundle savedInstanceState) { | ||||
| ||||
super.onCreate(savedInstanceState); | ||||
Bundle args = getArguments(); | ||||
| ||||
if (args != null && args.containsKey(ARG_FILTER)) { | ||||
String currentFilter = args.getString(ARG_FILTER, "open_created_by_me"); | ||||
String[] parts = currentFilter.split("_"); | ||||
selectedState = parts[0]; | ||||
selectedFilter = parts.length > 1 ? parts[1] : "created_by_me"; | ||||
} | ||||
} | ||||
| ||||
@Nullable @Override | ||||
public View onCreateView( | ||||
| @ -24,39 +49,68 @@ public class BottomSheetMyIssuesFilterFragment extends BottomSheetDialogFragment | |||
@Nullable ViewGroup container, | ||||
@Nullable Bundle savedInstanceState) { | ||||
| ||||
BottomSheetMyIssuesFilterBinding bottomSheetIssuesFilterBinding = | ||||
BottomSheetMyIssuesFilterBinding binding = | ||||
BottomSheetMyIssuesFilterBinding.inflate(inflater, container, false); | ||||
| ||||
bottomSheetIssuesFilterBinding.openMyIssues.setOnClickListener( | ||||
v1 -> { | ||||
bmListener.onButtonClicked("openMyIssues"); | ||||
dismiss(); | ||||
// Clear all chip states | ||||
binding.chipOpen.setChecked(false); | ||||
binding.chipClosed.setChecked(false); | ||||
binding.chipCreatedByMe.setChecked(false); | ||||
binding.chipAssignedToMe.setChecked(false); | ||||
| ||||
// Set chip states | ||||
binding.chipOpen.setChecked(selectedState.equals("open")); | ||||
binding.chipClosed.setChecked(selectedState.equals("closed")); | ||||
binding.chipCreatedByMe.setChecked(selectedFilter.equals("created_by_me")); | ||||
binding.chipAssignedToMe.setChecked(selectedFilter.equals("assignedToMe")); | ||||
| ||||
binding.stateChipGroup.setOnCheckedStateChangeListener( | ||||
(group, checkedIds) -> { | ||||
if (!checkedIds.isEmpty()) { | ||||
int checkedId = checkedIds.get(0); | ||||
if (checkedId == binding.chipOpen.getId()) { | ||||
selectedState = "open"; | ||||
} else if (checkedId == binding.chipClosed.getId()) { | ||||
selectedState = "closed"; | ||||
} | ||||
applyFilter(); | ||||
} | ||||
}); | ||||
| ||||
bottomSheetIssuesFilterBinding.closedMyIssues.setOnClickListener( | ||||
v12 -> { | ||||
bmListener.onButtonClicked("closedMyIssues"); | ||||
dismiss(); | ||||
binding.filterChipGroup.setOnCheckedStateChangeListener( | ||||
(group, checkedIds) -> { | ||||
if (!checkedIds.isEmpty()) { | ||||
| ||||
int checkedId = checkedIds.get(0); | ||||
if (checkedId == binding.chipCreatedByMe.getId()) { | ||||
selectedFilter = "created_by_me"; | ||||
} else if (checkedId == binding.chipAssignedToMe.getId()) { | ||||
selectedFilter = "assignedToMe"; | ||||
} | ||||
applyFilter(); | ||||
} | ||||
}); | ||||
| ||||
bottomSheetIssuesFilterBinding.assignedToMe.setOnClickListener( | ||||
v12 -> { | ||||
bmListener.onButtonClicked("assignedToMe"); | ||||
dismiss(); | ||||
}); | ||||
return binding.getRoot(); | ||||
} | ||||
| ||||
return bottomSheetIssuesFilterBinding.getRoot(); | ||||
private void applyFilter() { | ||||
String result = selectedState + "_" + selectedFilter; | ||||
bmListener.onButtonClicked(result); | ||||
dismiss(); | ||||
} | ||||
| ||||
@Override | ||||
public void onAttach(@NonNull Context context) { | ||||
| ||||
super.onAttach(context); | ||||
| ||||
try { | ||||
bmListener = (BottomSheetListener) context; | ||||
} catch (ClassCastException e) { | ||||
throw new ClassCastException(context + " must implement BottomSheetListener"); | ||||
} | ||||
} | ||||
| ||||
public interface BottomSheetListener { | ||||
void onButtonClicked(String text); | ||||
} | ||||
} | ||||
| |
| @ -252,6 +252,14 @@ public class BottomSheetSingleIssueFragment extends BottomSheetDialogFragment { | |||
dismiss(); | ||||
}); | ||||
| ||||
binding.trackedTime.setOnClickListener( | ||||
v -> { | ||||
BottomSheetTrackedTimeFragment trackedTimeSheet = | ||||
BottomSheetTrackedTimeFragment.newInstance(issue); | ||||
trackedTimeSheet.show(getParentFragmentManager(), "trackedTimeBottomSheet"); | ||||
dismiss(); | ||||
}); | ||||
| ||||
binding.subscribeIssue.setOnClickListener( | ||||
subscribeToIssue -> { | ||||
IssueActions.subscribe(ctx, issue); | ||||
| @ -286,6 +294,7 @@ public class BottomSheetSingleIssueFragment extends BottomSheetDialogFragment { | |||
binding.mergePullRequest.setVisibility(View.GONE); | ||||
binding.updatePullRequest.setVisibility(View.GONE); | ||||
binding.manageDependencies.setVisibility(View.GONE); | ||||
binding.trackedTime.setVisibility(View.GONE); | ||||
if (issue.getIssueType().equalsIgnoreCase("issue")) { | ||||
binding.issuePrDivider.setVisibility(View.GONE); | ||||
} | ||||
| @ -297,6 +306,7 @@ public class BottomSheetSingleIssueFragment extends BottomSheetDialogFragment { | |||
binding.addRemoveAssignees.setVisibility(View.GONE); | ||||
binding.editLabels.setVisibility(View.GONE); | ||||
binding.manageDependencies.setVisibility(View.GONE); | ||||
binding.trackedTime.setVisibility(View.GONE); | ||||
} | ||||
| ||||
binding.pinIssue.setOnClickListener( | ||||
| |
| @ -0,0 +1,276 @@ | |||
package org.mian.gitnex.fragments; | ||||
| ||||
import android.os.Bundle; | ||||
import android.view.LayoutInflater; | ||||
import android.view.View; | ||||
import android.view.ViewGroup; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.annotation.Nullable; | ||||
import androidx.recyclerview.widget.LinearLayoutManager; | ||||
import androidx.recyclerview.widget.RecyclerView; | ||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; | ||||
import java.util.ArrayList; | ||||
import java.util.Date; | ||||
import java.util.List; | ||||
import org.gitnex.tea4j.v2.models.AddTimeOption; | ||||
import org.gitnex.tea4j.v2.models.TrackedTime; | ||||
import org.mian.gitnex.R; | ||||
import org.mian.gitnex.adapters.TrackedTimeAdapter; | ||||
import org.mian.gitnex.clients.RetrofitClient; | ||||
import org.mian.gitnex.databinding.BottomSheetTrackedTimeBinding; | ||||
import org.mian.gitnex.helpers.Constants; | ||||
import org.mian.gitnex.helpers.Toasty; | ||||
import org.mian.gitnex.helpers.contexts.IssueContext; | ||||
import retrofit2.Call; | ||||
import retrofit2.Callback; | ||||
import retrofit2.Response; | ||||
| ||||
/** | ||||
* @author mmarif | ||||
*/ | ||||
public class BottomSheetTrackedTimeFragment extends BottomSheetDialogFragment { | ||||
| ||||
private BottomSheetTrackedTimeBinding binding; | ||||
private IssueContext issue; | ||||
private TrackedTimeAdapter trackedTimeAdapter; | ||||
private List<TrackedTime> trackedTimeList; | ||||
private int currentPage = 1; | ||||
private boolean isLoading = false; | ||||
private boolean hasMore = true; | ||||
private int resultLimit; | ||||
| ||||
public static BottomSheetTrackedTimeFragment newInstance(IssueContext issue) { | ||||
| ||||
BottomSheetTrackedTimeFragment fragment = new BottomSheetTrackedTimeFragment(); | ||||
Bundle args = new Bundle(); | ||||
args.putSerializable(IssueContext.INTENT_EXTRA, issue); | ||||
fragment.setArguments(args); | ||||
return fragment; | ||||
} | ||||
| ||||
@Nullable @Override | ||||
public View onCreateView( | ||||
@NonNull LayoutInflater inflater, | ||||
@Nullable ViewGroup container, | ||||
@Nullable Bundle savedInstanceState) { | ||||
| ||||
binding = BottomSheetTrackedTimeBinding.inflate(inflater, container, false); | ||||
| ||||
if (getArguments() != null) { | ||||
issue = (IssueContext) getArguments().getSerializable(IssueContext.INTENT_EXTRA); | ||||
} | ||||
if (issue == null) { | ||||
throw new IllegalStateException("IssueContext is required"); | ||||
} | ||||
| ||||
resultLimit = Constants.getCurrentResultLimit(requireContext()); | ||||
| ||||
trackedTimeList = new ArrayList<>(); | ||||
trackedTimeAdapter = new TrackedTimeAdapter(trackedTimeList); | ||||
LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext()); | ||||
binding.trackedTimeRecyclerView.setLayoutManager(layoutManager); | ||||
binding.trackedTimeRecyclerView.setAdapter(trackedTimeAdapter); | ||||
| ||||
trackedTimeAdapter.setOnDeleteClickListener(this::deleteTrackedTime); | ||||
| ||||
binding.trackedTimeRecyclerView.addOnScrollListener( | ||||
new RecyclerView.OnScrollListener() { | ||||
@Override | ||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { | ||||
super.onScrolled(recyclerView, dx, dy); | ||||
int totalItems = layoutManager.getItemCount(); | ||||
int lastVisibleItem = layoutManager.findLastVisibleItemPosition(); | ||||
if (!isLoading | ||||
&& hasMore | ||||
&& totalItems > 0 | ||||
&& lastVisibleItem >= totalItems - 5) { | ||||
currentPage++; | ||||
loadTrackedTimes(); | ||||
} | ||||
} | ||||
}); | ||||
| ||||
binding.addTimeButton.setEnabled(true); | ||||
binding.addTimeButton.setOnClickListener(v -> addTrackedTime()); | ||||
| ||||
loadTrackedTimes(); | ||||
| ||||
return binding.getRoot(); | ||||
} | ||||
| ||||
private void loadTrackedTimes() { | ||||
| ||||
if (isLoading) return; | ||||
isLoading = true; | ||||
| ||||
Call<List<TrackedTime>> call = | ||||
RetrofitClient.getApiInterface(requireContext()) | ||||
.issueTrackedTimes( | ||||
issue.getRepository().getOwner(), | ||||
issue.getRepository().getName(), | ||||
(long) issue.getIssueIndex(), | ||||
null, | ||||
null, | ||||
null, | ||||
currentPage, | ||||
resultLimit); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<List<TrackedTime>> call, | ||||
@NonNull Response<List<TrackedTime>> response) { | ||||
isLoading = false; | ||||
if (response.isSuccessful() && response.body() != null) { | ||||
List<TrackedTime> newTimes = response.body(); | ||||
int startPosition = trackedTimeList.size(); | ||||
trackedTimeList.addAll(newTimes); | ||||
trackedTimeAdapter.notifyItemRangeInserted( | ||||
startPosition, newTimes.size()); | ||||
hasMore = newTimes.size() >= resultLimit; | ||||
updateTotalTime(); | ||||
updateUI(); | ||||
} else { | ||||
hasMore = false; | ||||
updateUI(); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure( | ||||
@NonNull Call<List<TrackedTime>> call, @NonNull Throwable t) { | ||||
isLoading = false; | ||||
hasMore = false; | ||||
updateUI(); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
private void addTrackedTime() { | ||||
| ||||
String hoursStr = | ||||
binding.hoursInput.getText() != null ? binding.hoursInput.getText().toString() : ""; | ||||
String minutesStr = | ||||
binding.minutesInput.getText() != null | ||||
? binding.minutesInput.getText().toString() | ||||
: ""; | ||||
| ||||
int hours = hoursStr.isEmpty() ? 0 : Integer.parseInt(hoursStr); | ||||
int minutes = minutesStr.isEmpty() ? 0 : Integer.parseInt(minutesStr); | ||||
| ||||
if (hours == 0 && minutes == 0) { | ||||
Toasty.warning(requireContext(), getString(R.string.enter_time)); | ||||
return; | ||||
} | ||||
| ||||
long totalSeconds = (hours * 3600L) + (minutes * 60L); | ||||
| ||||
AddTimeOption timeOption = new AddTimeOption(); | ||||
timeOption.setCreated(new Date()); | ||||
timeOption.setTime(totalSeconds); | ||||
| ||||
Call<TrackedTime> call = | ||||
RetrofitClient.getApiInterface(requireContext()) | ||||
.issueAddTime( | ||||
issue.getRepository().getOwner(), | ||||
issue.getRepository().getName(), | ||||
(long) issue.getIssueIndex(), | ||||
timeOption); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<TrackedTime> call, | ||||
@NonNull Response<TrackedTime> response) { | ||||
if (response.isSuccessful() && response.body() != null) { | ||||
TrackedTime newTime = response.body(); | ||||
trackedTimeList.add(0, newTime); | ||||
trackedTimeAdapter.notifyItemInserted(0); | ||||
binding.trackedTimeRecyclerView.scrollToPosition(0); | ||||
binding.hoursInput.setText(""); | ||||
binding.minutesInput.setText(""); | ||||
updateTotalTime(); | ||||
updateUI(); | ||||
Toasty.success(requireContext(), getString(R.string.time_added)); | ||||
} else { | ||||
Toasty.error(requireContext(), getString(R.string.time_add_failed)); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<TrackedTime> call, @NonNull Throwable t) { | ||||
Toasty.error( | ||||
requireContext(), getString(R.string.genericServerResponseError)); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
private void deleteTrackedTime(TrackedTime time, int position) { | ||||
| ||||
Call<Void> call = | ||||
RetrofitClient.getApiInterface(requireContext()) | ||||
.issueDeleteTime( | ||||
issue.getRepository().getOwner(), | ||||
issue.getRepository().getName(), | ||||
(long) issue.getIssueIndex(), | ||||
time.getId()); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<Void> call, @NonNull Response<Void> response) { | ||||
| ||||
if (response.isSuccessful()) { | ||||
| ||||
trackedTimeList.remove(position); | ||||
trackedTimeAdapter.notifyItemRemoved(position); | ||||
trackedTimeAdapter.notifyItemRangeChanged( | ||||
position, trackedTimeList.size()); | ||||
updateTotalTime(); | ||||
updateUI(); | ||||
Toasty.success(requireContext(), getString(R.string.time_removed)); | ||||
} else { | ||||
Toasty.error(requireContext(), getString(R.string.time_delete_failed)); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) { | ||||
Toasty.error( | ||||
requireContext(), getString(R.string.genericServerResponseError)); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
private void updateTotalTime() { | ||||
| ||||
long totalSeconds = 0; | ||||
for (TrackedTime time : trackedTimeList) { | ||||
totalSeconds += time.getTime(); | ||||
} | ||||
long hours = totalSeconds / 3600; | ||||
long minutes = (totalSeconds % 3600) / 60; | ||||
long seconds = totalSeconds % 60; | ||||
binding.totalTrackedTime.setText(getString(R.string.total_time, hours, minutes, seconds)); | ||||
} | ||||
| ||||
private void updateUI() { | ||||
| ||||
if (trackedTimeList.isEmpty()) { | ||||
binding.trackedTimeRecyclerView.setVisibility(View.GONE); | ||||
binding.noTrackedTimeText.setVisibility(View.VISIBLE); | ||||
} else { | ||||
binding.trackedTimeRecyclerView.setVisibility(View.VISIBLE); | ||||
binding.noTrackedTimeText.setVisibility(View.GONE); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onDestroyView() { | ||||
super.onDestroyView(); | ||||
binding = null; | ||||
} | ||||
} |
| @ -364,18 +364,10 @@ public class IssuesFragment extends Fragment { | |||
pinnedIssuesList.clear(); | ||||
pinnedIssuesList.addAll(response.body()); | ||||
adapterPinned.notifyDataChanged(); | ||||
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE); | ||||
} else { | ||||
pinnedIssuesList.clear(); | ||||
adapterPinned.notifyDataChanged(); | ||||
fragmentIssuesBinding.noDataIssues.setVisibility(View.VISIBLE); | ||||
} | ||||
fragmentIssuesBinding.progressBar.setVisibility(View.GONE); | ||||
} else if (response.code() == 404) { | ||||
fragmentIssuesBinding.noDataIssues.setVisibility(View.VISIBLE); | ||||
fragmentIssuesBinding.progressBar.setVisibility(View.GONE); | ||||
} else { | ||||
Toasty.error(context, getString(R.string.genericError)); | ||||
} | ||||
} | ||||
| ||||
| @ -397,6 +389,8 @@ public class IssuesFragment extends Fragment { | |||
String labels, | ||||
String mentionedBy) { | ||||
| ||||
fragmentIssuesBinding.progressBar.setVisibility(View.VISIBLE); | ||||
| ||||
Call<List<Issue>> call = | ||||
RetrofitClient.getApiInterface(context) | ||||
.issueListIssues( | ||||
| |
| @ -11,6 +11,7 @@ import android.view.View; | |||
import android.view.ViewGroup; | ||||
import android.view.inputmethod.EditorInfo; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.annotation.Nullable; | ||||
import androidx.core.view.MenuProvider; | ||||
import androidx.fragment.app.Fragment; | ||||
import androidx.lifecycle.Lifecycle; | ||||
| @ -27,20 +28,55 @@ import org.mian.gitnex.viewmodels.IssuesViewModel; | |||
*/ | ||||
public class MyIssuesFragment extends Fragment { | ||||
| ||||
public String state = "open"; | ||||
public boolean assignedToMe = false; | ||||
private IssuesViewModel issuesViewModel; | ||||
private FragmentIssuesBinding fragmentIssuesBinding; | ||||
private IssuesViewModel issuesViewModel; | ||||
private ExploreIssuesAdapter adapter; | ||||
private int page = 1; | ||||
private Menu menu; | ||||
private String state = "open"; | ||||
private boolean assignedToMe = false; | ||||
private boolean createdByMe = true; | ||||
private int page = 1; | ||||
| ||||
@Override | ||||
@Nullable @Override | ||||
public View onCreateView( | ||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | ||||
@NonNull LayoutInflater inflater, | ||||
@Nullable ViewGroup container, | ||||
@Nullable Bundle savedInstanceState) { | ||||
| ||||
fragmentIssuesBinding = FragmentIssuesBinding.inflate(inflater, container, false); | ||||
issuesViewModel = new ViewModelProvider(this).get(IssuesViewModel.class); | ||||
| ||||
fragmentIssuesBinding.recyclerView.setHasFixedSize(true); | ||||
fragmentIssuesBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
fragmentIssuesBinding.createNewIssue.setVisibility(View.GONE); | ||||
| ||||
setupMenu(); | ||||
| ||||
((MainActivity) requireActivity()) | ||||
.setFragmentRefreshListenerMyIssues( | ||||
myIssues -> { | ||||
updateFilterState(myIssues); | ||||
fetchDataAsync(null); | ||||
}); | ||||
| ||||
fragmentIssuesBinding.pullToRefresh.setOnRefreshListener( | ||||
() -> | ||||
new Handler(Looper.getMainLooper()) | ||||
.postDelayed( | ||||
() -> { | ||||
page = 1; | ||||
fragmentIssuesBinding.pullToRefresh.setRefreshing( | ||||
false); | ||||
fetchDataAsync(null); | ||||
}, | ||||
50)); | ||||
| ||||
fetchDataAsync(null); | ||||
| ||||
return fragmentIssuesBinding.getRoot(); | ||||
} | ||||
| ||||
private void setupMenu() { | ||||
requireActivity() | ||||
.addMenuProvider( | ||||
new MenuProvider() { | ||||
| @ -65,7 +101,7 @@ public class MyIssuesFragment extends Fragment { | |||
| ||||
@Override | ||||
public boolean onQueryTextSubmit(String query) { | ||||
fetchDataAsync(query, state, assignedToMe); | ||||
fetchDataAsync(query); | ||||
searchView.setQuery(null, false); | ||||
searchItem.collapseActionView(); | ||||
return false; | ||||
| @ -80,81 +116,74 @@ public class MyIssuesFragment extends Fragment { | |||
| ||||
@Override | ||||
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { | ||||
| ||||
if (menuItem.getItemId() == R.id.filter) { | ||||
| ||||
String currentFilter = | ||||
state | ||||
+ "_" | ||||
+ (assignedToMe | ||||
? "assignedToMe" | ||||
: "created_by_me"); | ||||
BottomSheetMyIssuesFilterFragment bottomSheet = | ||||
BottomSheetMyIssuesFilterFragment.newInstance( | ||||
currentFilter); | ||||
bottomSheet.show(getParentFragmentManager(), "myIssuesFilter"); | ||||
| ||||
return true; | ||||
} | ||||
return false; | ||||
} | ||||
}, | ||||
getViewLifecycleOwner(), | ||||
Lifecycle.State.RESUMED); | ||||
| ||||
issuesViewModel = new ViewModelProvider(this).get(IssuesViewModel.class); | ||||
| ||||
fragmentIssuesBinding.recyclerView.setHasFixedSize(true); | ||||
fragmentIssuesBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
| ||||
fragmentIssuesBinding.createNewIssue.setVisibility(View.GONE); | ||||
| ||||
((MainActivity) requireActivity()) | ||||
.setFragmentRefreshListenerMyIssues( | ||||
myIssues -> { | ||||
state = myIssues; | ||||
if (state.equals("closed")) { | ||||
menu.getItem(1).setIcon(R.drawable.ic_filter_closed); | ||||
} else { | ||||
menu.getItem(1).setIcon(R.drawable.ic_filter); | ||||
} | ||||
| ||||
assignedToMe = state.equals("assignedToMe"); | ||||
| ||||
fragmentIssuesBinding.progressBar.setVisibility(View.VISIBLE); | ||||
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE); | ||||
| ||||
fetchDataAsync(null, state, assignedToMe); | ||||
}); | ||||
| ||||
fragmentIssuesBinding.pullToRefresh.setOnRefreshListener( | ||||
() -> | ||||
new Handler(Looper.getMainLooper()) | ||||
.postDelayed( | ||||
() -> { | ||||
page = 1; | ||||
fragmentIssuesBinding.pullToRefresh.setRefreshing( | ||||
false); | ||||
issuesViewModel.loadIssuesList( | ||||
null, | ||||
"issues", | ||||
true, | ||||
state, | ||||
assignedToMe, | ||||
getContext()); | ||||
fragmentIssuesBinding.progressBar.setVisibility( | ||||
View.VISIBLE); | ||||
}, | ||||
50)); | ||||
| ||||
fetchDataAsync(null, state, assignedToMe); | ||||
| ||||
return fragmentIssuesBinding.getRoot(); | ||||
} | ||||
| ||||
private void fetchDataAsync(String query, String state, boolean assignedToMe) { | ||||
public String getCurrentFilter() { | ||||
return state + "_" + (assignedToMe ? "assignedToMe" : "created_by_me"); | ||||
} | ||||
| ||||
private void updateFilterState(String filter) { | ||||
String[] parts = filter.split("_"); | ||||
String stateValue = parts[0]; | ||||
String filterValue = parts[1]; | ||||
| ||||
state = stateValue; | ||||
menu.getItem(1) | ||||
.setIcon( | ||||
state.equals("closed") | ||||
? R.drawable.ic_filter_closed | ||||
: R.drawable.ic_filter); | ||||
| ||||
if (filterValue.equals("created_by_me")) { | ||||
createdByMe = true; | ||||
assignedToMe = false; | ||||
} else if (filterValue.equals("assignedToMe")) { | ||||
createdByMe = false; | ||||
assignedToMe = true; | ||||
} | ||||
} | ||||
| ||||
private void fetchDataAsync(String query) { | ||||
| ||||
fragmentIssuesBinding.progressBar.setVisibility(View.VISIBLE); | ||||
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE); | ||||
| ||||
issuesViewModel | ||||
.getIssuesList(query, "issues", true, state, assignedToMe, getContext()) | ||||
.getIssuesList(query, "issues", createdByMe, state, assignedToMe, getContext()) | ||||
.observe( | ||||
getViewLifecycleOwner(), | ||||
issuesListMain -> { | ||||
adapter = new ExploreIssuesAdapter(issuesListMain, getContext()); | ||||
adapter.setLoadMoreListener( | ||||
new ExploreIssuesAdapter.OnLoadMoreListener() { | ||||
| ||||
@Override | ||||
public void onLoadMore() { | ||||
| ||||
page += 1; | ||||
issuesViewModel.loadMoreIssues( | ||||
query, | ||||
"issues", | ||||
true, | ||||
createdByMe, | ||||
state, | ||||
page, | ||||
assignedToMe, | ||||
| @ -166,7 +195,6 @@ public class MyIssuesFragment extends Fragment { | |||
| ||||
@Override | ||||
public void onLoadFinished() { | ||||
| ||||
fragmentIssuesBinding.progressBar.setVisibility( | ||||
View.GONE); | ||||
} | ||||
| |
34 app/src/main/res/drawable/ic_stopwatch.xml Normal file
34
app/src/main/res/drawable/ic_stopwatch.xml Normal file | @ -0,0 +1,34 @@ | |||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
android:width="24dp" | ||||
android:height="24dp" | ||||
android:viewportWidth="24" | ||||
android:viewportHeight="24"> | ||||
<path | ||||
android:pathData="M5,13a7,7 0,1 0,14 0a7,7 0,0 0,-14 0z" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
<path | ||||
android:pathData="M14.5,10.5l-2.5,2.5" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
<path | ||||
android:pathData="M17,8l1,-1" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
<path | ||||
android:pathData="M14,3h-4" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
</vector> |
| @ -49,12 +49,11 @@ | |||
android:layout_height="wrap_content" | ||||
app:singleSelection="true" | ||||
android:paddingStart="@dimen/dimen16dp" | ||||
android:paddingEnd="@dimen/dimen16dp" | ||||
android:paddingBottom="@dimen/dimen8dp"> | ||||
android:paddingEnd="@dimen/dimen16dp"> | ||||
| ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/openChip" | ||||
style="@style/Widget.MaterialComponents.Chip.Filter" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/isOpen" | ||||
| @ -62,7 +61,7 @@ | |||
| ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/closedChip" | ||||
style="@style/Widget.MaterialComponents.Chip.Filter" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/isClosed"/> | ||||
| @ -78,21 +77,21 @@ | |||
| ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/mentionsChip" | ||||
style="@style/Widget.MaterialComponents.Chip.Filter" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/im_mentioned"/> | ||||
| ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/labelsChip" | ||||
style="@style/Widget.MaterialComponents.Chip.Filter" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/newIssueLabelsTitle"/> | ||||
| ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/milestoneChip" | ||||
style="@style/Widget.MaterialComponents.Chip.Filter" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/tabTextMl" | ||||
| |
| @ -9,103 +9,90 @@ | |||
android:paddingTop="@dimen/dimen6dp" | ||||
android:paddingBottom="@dimen/dimen12dp"> | ||||
| ||||
<androidx.core.widget.NestedScrollView | ||||
<LinearLayout | ||||
android:id="@+id/myIssuesFilterHeadFrame" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content"> | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical" | ||||
android:padding="@dimen/dimen8dp"> | ||||
| ||||
<LinearLayout | ||||
<TextView | ||||
android:id="@+id/bottomSheetHeader" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical"> | ||||
android:gravity="center" | ||||
android:text="@string/strFilter" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp"/> | ||||
| ||||
<LinearLayout | ||||
android:id="@+id/myIssuesFilterHeadFrame" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical" | ||||
android:padding="@dimen/dimen8dp"> | ||||
<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"> | ||||
| ||||
<TextView | ||||
android:id="@+id/bottomSheetHeader" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:gravity="center" | ||||
android:text="@string/strFilter" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp"/> | ||||
| ||||
<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> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<com.google.android.flexbox.FlexboxLayout | ||||
android:id="@+id/myIssuesSection" | ||||
<View | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
android:padding="@dimen/dimen4dp" | ||||
app:alignContent="center" | ||||
app:alignItems="flex_start" | ||||
app:flexWrap="wrap" | ||||
app:justifyContent="center"> | ||||
android:background="?attr/fabColor" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/openMyIssues" | ||||
android:layout_width="@dimen/dimen132dp" | ||||
android:layout_height="@dimen/dimen100dp" | ||||
android:background="?android:attr/selectableItemBackgroundBorderless" | ||||
android:gravity="center" | ||||
android:padding="@dimen/dimen4dp" | ||||
android:text="@string/isOpen" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" | ||||
app:drawableTopCompat="@drawable/ic_issue" | ||||
app:layout_alignSelf="flex_start"/> | ||||
</com.google.android.material.card.MaterialCardView> | ||||
| ||||
<TextView | ||||
android:id="@+id/closedMyIssues" | ||||
android:layout_width="@dimen/dimen132dp" | ||||
android:layout_height="@dimen/dimen100dp" | ||||
android:background="?android:attr/selectableItemBackgroundBorderless" | ||||
android:gravity="center" | ||||
android:padding="@dimen/dimen4dp" | ||||
android:text="@string/isClosed" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" | ||||
app:drawableTopCompat="@drawable/ic_issue_closed" | ||||
app:layout_alignSelf="flex_start"/> | ||||
</LinearLayout> | ||||
| ||||
<TextView | ||||
android:id="@+id/assignedToMe" | ||||
android:layout_width="@dimen/dimen132dp" | ||||
android:layout_height="@dimen/dimen100dp" | ||||
android:background="?android:attr/selectableItemBackgroundBorderless" | ||||
android:gravity="center" | ||||
android:padding="@dimen/dimen4dp" | ||||
android:text="@string/assignedToMe" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" | ||||
app:drawableTopCompat="@drawable/ic_person" | ||||
app:layout_alignSelf="flex_start"/> | ||||
<com.google.android.material.chip.ChipGroup | ||||
android:id="@+id/stateChipGroup" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:paddingStart="@dimen/dimen16dp" | ||||
android:paddingEnd="@dimen/dimen16dp" | ||||
app:singleSelection="true" | ||||
app:selectionRequired="true"> | ||||
| ||||
</com.google.android.flexbox.FlexboxLayout> | ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/chipOpen" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/isOpen" | ||||
android:checked="true"/> | ||||
| ||||
</LinearLayout> | ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/chipClosed" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/isClosed"/> | ||||
| ||||
</androidx.core.widget.NestedScrollView> | ||||
</com.google.android.material.chip.ChipGroup> | ||||
| ||||
<com.google.android.material.chip.ChipGroup | ||||
android:id="@+id/filterChipGroup" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:padding="@dimen/dimen16dp" | ||||
app:singleSelection="true" | ||||
app:selectionRequired="true"> | ||||
| ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/chipCreatedByMe" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/created_by_me" | ||||
android:checked="true"/> | ||||
| ||||
<com.google.android.material.chip.Chip | ||||
android:id="@+id/chipAssignedToMe" | ||||
style="@style/Widget.Material3.Chip.Filter" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/assignedToMe"/> | ||||
| ||||
</com.google.android.material.chip.ChipGroup> | ||||
| ||||
</LinearLayout> | ||||
| |
| @ -176,6 +176,19 @@ | |||
app:drawableTopCompat="@drawable/ic_dependencies" | ||||
app:layout_alignSelf="flex_start" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/tracked_time" | ||||
android:layout_width="@dimen/dimen132dp" | ||||
android:layout_height="@dimen/dimen100dp" | ||||
android:background="?android:attr/selectableItemBackgroundBorderless" | ||||
android:gravity="center" | ||||
android:padding="@dimen/dimen4dp" | ||||
android:text="@string/tracked_time" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" | ||||
app:drawableTopCompat="@drawable/ic_stopwatch" | ||||
app:layout_alignSelf="flex_start" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/pin_issue" | ||||
android:layout_width="@dimen/dimen132dp" | ||||
| |
108 app/src/main/res/layout/bottom_sheet_tracked_time.xml Normal file
108
app/src/main/res/layout/bottom_sheet_tracked_time.xml Normal file | @ -0,0 +1,108 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | ||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
xmlns:tools="http://schemas.android.com/tools" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical" | ||||
android:padding="@dimen/dimen16dp"> | ||||
| ||||
<TextView | ||||
android:id="@+id/bottomSheetHeader" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/tracked_time" | ||||
android:textAppearance="?attr/textAppearanceTitleLarge" | ||||
android:textColor="?attr/colorOnSurface" | ||||
android:paddingBottom="@dimen/dimen16dp" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/totalTrackedTime" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:textAppearance="?attr/textAppearanceBodyMedium" | ||||
android:textStyle="bold" | ||||
android:textColor="?attr/colorOnSurface" | ||||
android:paddingBottom="@dimen/dimen12dp" /> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="horizontal" | ||||
android:gravity="center_vertical"> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/hoursInputLayout" | ||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense" | ||||
android:layout_width="0dp" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight="1" | ||||
android:hint="@string/hours" | ||||
android:layout_marginEnd="@dimen/dimen12dp"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/hoursInput" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="@dimen/dimen40dp" | ||||
android:inputType="number" | ||||
android:maxLength="3" | ||||
android:textAppearance="?attr/textAppearanceBodyMedium" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/minutesInputLayout" | ||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense" | ||||
android:layout_width="0dp" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight="1" | ||||
android:hint="@string/minutes" | ||||
android:layout_marginEnd="@dimen/dimen12dp"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/minutesInput" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="@dimen/dimen40dp" | ||||
android:inputType="number" | ||||
android:maxLength="2" | ||||
android:textAppearance="?attr/textAppearanceBodyMedium" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.button.MaterialButton | ||||
android:id="@+id/addTimeButton" | ||||
android:layout_width="@dimen/dimen96dp" | ||||
android:layout_height="@dimen/dimen50dp" | ||||
android:text="@string/addButton" | ||||
android:minWidth="0dp" | ||||
android:paddingHorizontal="@dimen/dimen16dp" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<com.google.android.material.divider.MaterialDivider | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginVertical="@dimen/dimen16dp" | ||||
app:dividerColor="?attr/colorOutlineVariant" /> | ||||
| ||||
<androidx.recyclerview.widget.RecyclerView | ||||
android:id="@+id/trackedTimeRecyclerView" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:overScrollMode="never" | ||||
tools:listitem="@layout/list_tracked_time" | ||||
tools:itemCount="2" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/noTrackedTimeText" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/no_tracked_time" | ||||
android:textAppearance="?attr/textAppearanceBodyMedium" | ||||
android:textColor="?attr/colorOnSurfaceVariant" | ||||
android:gravity="center" | ||||
android:padding="@dimen/dimen16dp" | ||||
android:visibility="visible" /> | ||||
| ||||
</LinearLayout> |
82 app/src/main/res/layout/list_tracked_time.xml Normal file
82
app/src/main/res/layout/list_tracked_time.xml Normal file | @ -0,0 +1,82 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | ||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
xmlns:tools="http://schemas.android.com/tools" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="horizontal" | ||||
android:paddingBottom="@dimen/dimen4dp" | ||||
android:gravity="center_vertical"> | ||||
| ||||
<com.google.android.material.card.MaterialCardView | ||||
android:id="@+id/card_view" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
style="?attr/materialCardViewElevatedStyle" | ||||
app:cardElevation="@dimen/dimen0dp"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:foreground="?android:attr/selectableItemBackground" | ||||
android:background="?attr/materialCardBackgroundColor" | ||||
android:padding="@dimen/dimen12dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="horizontal"> | ||||
| ||||
<!-- User Avatar --> | ||||
<com.google.android.material.card.MaterialCardView | ||||
android:id="@+id/avatarFrame" | ||||
android:layout_width="@dimen/dimen32dp" | ||||
android:layout_height="@dimen/dimen32dp" | ||||
style="?attr/materialCardViewElevatedStyle" | ||||
android:backgroundTint="@android:color/transparent" | ||||
app:cardElevation="@dimen/dimen0dp" | ||||
android:layout_marginEnd="@dimen/dimen12dp" | ||||
app:cardCornerRadius="@dimen/dimen8dp"> | ||||
| ||||
<ImageView | ||||
android:id="@+id/userAvatar" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
android:scaleType="centerCrop" | ||||
android:src="@drawable/ic_person" /> | ||||
| ||||
</com.google.android.material.card.MaterialCardView> | ||||
| ||||
<!-- User Name --> | ||||
<TextView | ||||
android:id="@+id/userName" | ||||
android:layout_width="0dp" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight="1" | ||||
android:textAppearance="?attr/textAppearanceBodyMedium" | ||||
android:textColor="?attr/colorOnSurface" | ||||
android:layout_marginEnd="@dimen/dimen4dp" | ||||
tools:text="M M Arif - mian" /> | ||||
| ||||
<!-- Tracked Time --> | ||||
<TextView | ||||
android:id="@+id/trackedTimeEntry" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:textAppearance="?attr/textAppearanceBodyMedium" | ||||
android:textColor="?attr/colorOnSurface" | ||||
android:layout_marginEnd="@dimen/dimen8dp" | ||||
tools:text="0h 30m 0s" /> | ||||
| ||||
<!-- Delete Icon --> | ||||
<ImageView | ||||
android:id="@+id/deleteTrackedTime" | ||||
android:layout_width="@dimen/dimen22dp" | ||||
android:layout_height="@dimen/dimen22dp" | ||||
android:src="@drawable/ic_delete" | ||||
android:contentDescription="@string/menuDeleteText" | ||||
app:tint="?attr/colorOnSurfaceVariant" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</com.google.android.material.card.MaterialCardView> | ||||
| ||||
</LinearLayout> |
| @ -155,6 +155,7 @@ | |||
<string name="commentButtonText">Comment</string> | ||||
<string name="commentEmptyError">Please write your comment</string> | ||||
<string name="commentSuccess">Comment posted</string> | ||||
<string name="created_by_me">Created by Me</string> | ||||
| ||||
<string name="featureDeprecated">This function will be removed in the future</string> | ||||
<string name="screamingInFearEmoticon" translatable="false">😱</string> | ||||
| @ -554,6 +555,8 @@ | |||
<string name="branch">Branch</string> | ||||
<string name="branches">Branches</string> | ||||
<string name="timeline">Timeline</string> | ||||
<string name="hours">Hours</string> | ||||
<string name="minutes">Minutes</string> | ||||
<!-- generic copy --> | ||||
| ||||
<string name="exploreUsers">Explore users</string> | ||||
| @ -956,4 +959,13 @@ | |||
<string name="dependency_add_failed">Failed to add dependency</string> | ||||
<string name="search_failed">Search failed</string> | ||||
<string name="no_dependency_search_results">No matching results found</string> | ||||
| ||||
<string name="tracked_time">Tracked Time</string> | ||||
<string name="no_tracked_time">No time tracked yet</string> | ||||
<string name="total_time">Total: %1$dh %2$dm %3$ds</string> | ||||
<string name="enter_time">Please enter some time</string> | ||||
<string name="time_added">Time added</string> | ||||
<string name="time_removed">Time removed</string> | ||||
<string name="time_add_failed">Failed to add time</string> | ||||
<string name="time_delete_failed">Failed to delete time</string> | ||||
</resources> | ||||
| |
Loading…
Add table
Add a link
Reference in a new issue