Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.google.android.material.button.MaterialButtonToggleGroup;

import org.appdevforall.k2go.R;
import org.appdevforall.k2go.contentexchange.data.ShareHandshake;
import org.appdevforall.k2go.contentexchange.domain.ShareTransport;
import org.appdevforall.k2go.hotspot.HotspotPermissions;
import org.appdevforall.k2go.hotspot.LocalHotspotManager;
import org.appdevforall.k2go.hotspot.data.ApAddressResolver;
import org.appdevforall.k2go.redesign.QrSection;
import org.appdevforall.k2go.redesign.SegmentedTabs;
import org.appdevforall.k2go.sync.transport.NetworkStateLiveData;

public class ContentShareFragment extends Fragment {
Expand All @@ -42,8 +41,9 @@ public class ContentShareFragment extends Fragment {
private View receiveRoot;
private QrSection secJoin;
private QrSection secReceive;
private TextView transportLabel;
private MaterialButtonToggleGroup transportToggle;
private View transportRow;
private TextView transportHotspot;
private TextView transportWifi;
private Button done;

private final ShareSession session = ContentShareService.session();
Expand Down Expand Up @@ -77,19 +77,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
((TextView) receiveRoot.findViewById(R.id.sec_badge)).setText("2");
((TextView) receiveRoot.findViewById(R.id.sec_title)).setText(R.string.k2go_cx_share_step_receive);

transportLabel = root.findViewById(R.id.k2go_cxs_transport_label);
transportToggle = root.findViewById(R.id.k2go_cxs_transport);
transportToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
if (!isChecked) {
return; // react only to the newly selected button, not the one being cleared
}
boolean wifi = checkedId == R.id.k2go_cxs_transport_wifi;
if (wifi == useWifiAddress) {
return; // no change (also absorbs the programmatic sync in renderInvite, no re-render loop)
}
useWifiAddress = wifi;
renderInvite(); // re-encode the receive QR at the chosen address
});
transportRow = root.findViewById(R.id.k2go_cxs_transport);
transportHotspot = root.findViewById(R.id.k2go_cxs_transport_hotspot);
transportWifi = root.findViewById(R.id.k2go_cxs_transport_wifi);
transportHotspot.setOnClickListener(v -> selectTransport(false));
transportWifi.setOnClickListener(v -> selectTransport(true));

done.setOnClickListener(v -> {
ContentShareService.cancel(requireContext());
Expand Down Expand Up @@ -185,8 +177,7 @@ private void renderInvite() {
sec.subCaption.setText("");
sec.setFallback(requireContext(), null);
}
transportLabel.setVisibility(View.GONE);
transportToggle.setVisibility(View.GONE);
transportRow.setVisibility(View.GONE);
waiting.setVisibility(View.GONE);
return;
}
Expand All @@ -197,20 +188,17 @@ private void renderInvite() {
String wifiIp = ApAddressResolver.resolveStationWifiIp(requireContext());

// Transport selector: a choice only when BOTH addresses exist and differ. With one, use it and
// hide the toggle (the common case). The hotspot is the default (fast, direct); Wi-Fi lets two
// hide the strip (the common case). The hotspot is the default (fast, direct); Wi-Fi lets two
// phones already on the same network pull over it, avoiding the Wi-Fi Direct link. When Wi-Fi is
// chosen there is nothing to join, so the join-hotspot section is hidden too.
boolean both = hotspotIp != null && wifiIp != null && !hotspotIp.equals(wifiIp);
transportLabel.setVisibility(both ? View.VISIBLE : View.GONE);
transportToggle.setVisibility(both ? View.VISIBLE : View.GONE);
// The selection persists across a Wi-Fi blip: on Wi-Fi loss the toggle just hides and the QR
transportRow.setVisibility(both ? View.VISIBLE : View.GONE);
// The selection persists across a Wi-Fi blip: on Wi-Fi loss the strip just hides and the QR
// falls back to whatever address is left; when Wi-Fi returns (a new DHCP IP), the choice is
// still Wi-Fi and the QR redraws at the fresh address, instead of silently reverting to hotspot.
if (both) {
int want = useWifiAddress ? R.id.k2go_cxs_transport_wifi : R.id.k2go_cxs_transport_hotspot;
if (transportToggle.getCheckedButtonId() != want) {
transportToggle.check(want); // sync UI to the field; the listener no-ops (state matches)
}
SegmentedTabs.paint(transportHotspot, !useWifiAddress);
SegmentedTabs.paint(transportWifi, useWifiAddress);
}
joinRoot.setVisibility(both && useWifiAddress ? View.GONE : View.VISIBLE);
String ip = both ? (useWifiAddress ? wifiIp : hotspotIp)
Expand Down Expand Up @@ -241,6 +229,15 @@ private void renderInvite() {
}
}

/** Pick the address the receive QR carries and repaint the strip (Connect/Clone mode pattern). */
private void selectTransport(boolean wifi) {
if (wifi == useWifiAddress) {
return; // already selected; no re-encode
}
useWifiAddress = wifi;
renderInvite(); // re-encode the receive QR at the chosen address and repaint the strip
}

/** Show a single status line (preparing / hotspot / error) instead of the two QR cards. */
private void showStatus(String text) {
status.setText(text);
Expand All @@ -249,8 +246,7 @@ private void showStatus(String text) {
secReceive.stopPending();
joinRoot.setVisibility(View.GONE);
receiveRoot.setVisibility(View.GONE);
transportLabel.setVisibility(View.GONE);
transportToggle.setVisibility(View.GONE);
transportRow.setVisibility(View.GONE);
waiting.setVisibility(View.GONE);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;

import com.airbnb.lottie.LottieAnimationView;
import com.google.android.material.progressindicator.LinearProgressIndicator;

import org.appdevforall.k2go.R;
import org.appdevforall.k2go.contentexchange.domain.ExportJob;
import org.appdevforall.k2go.util.ByteFormatter;
import org.appdevforall.k2go.util.Motion;

import java.util.Collections;
import java.util.List;
Expand All @@ -50,6 +52,9 @@ public class ExportProgressFragment extends Fragment {
private Button cancel;
private Button retry;
private Button finish;
private View animBox;
private LottieAnimationView anim;
private boolean reduceMotion;

private final ExportSession session = ContentExportService.session();

Expand All @@ -74,6 +79,20 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
retry = root.findViewById(R.id.k2go_cxx_retry);
finish = root.findViewById(R.id.k2go_cxx_finish);

// Two-phones transfer animation, shared with Clone (R.raw.k2go_clone_loop). Reduce motion holds
// a single frame instead of looping; the screen never depends on movement to be understood.
animBox = root.findViewById(R.id.k2go_cxx_anim_box);
anim = root.findViewById(R.id.k2go_cxx_anim);
reduceMotion = Motion.reduced(getContext());
if (anim != null) {
anim.setAnimation(R.raw.k2go_clone_loop);
if (reduceMotion) {
anim.setProgress(0.5f);
} else {
anim.setRepeatCount(com.airbnb.lottie.LottieDrawable.INFINITE);
}
}

cancel.setOnClickListener(v -> ContentExportService.cancel(requireContext()));
retry.setOnClickListener(v -> ContentExportService.retry(requireContext()));
finish.setOnClickListener(v -> {
Expand All @@ -98,6 +117,9 @@ public void onResume() {
public void onPause() {
super.onPause();
session.setListener(null);
if (anim != null && anim.isAnimating()) {
anim.pauseAnimation();
}
}

private void render() {
Expand Down Expand Up @@ -129,6 +151,24 @@ private void render() {
boolean settled = !running;
retry.setVisibility(settled && session.canRetry() ? View.VISIBLE : View.GONE);
finish.setVisibility(settled && !preparing ? View.VISIBLE : View.GONE);
renderAnim(running || preparing);
}

/** Show the transfer animation only while the transfer is in flight; play it unless motion is off. */
private void renderAnim(boolean active) {
if (animBox != null) {
animBox.setVisibility(active ? View.VISIBLE : View.GONE);
}
if (anim == null) {
return;
}
if (active && !reduceMotion) {
if (!anim.isAnimating()) {
anim.playAnimation();
}
} else if (anim.isAnimating()) {
anim.pauseAnimation();
}
}

private void renderCurrentItem(boolean running, boolean preparing) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.airbnb.lottie.LottieAnimationView;
import com.google.android.material.progressindicator.LinearProgressIndicator;

import org.appdevforall.k2go.R;
import org.appdevforall.k2go.contentexchange.domain.ExportJob;
import org.appdevforall.k2go.util.ByteFormatter;
import org.appdevforall.k2go.util.Motion;

import java.util.Locale;

public class ImportProgressFragment extends Fragment {

private TextView title;
private TextView source;
private TextView overallLabel;
private LinearProgressIndicator overallBar;
Expand All @@ -42,6 +45,9 @@ public class ImportProgressFragment extends Fragment {
private Button cancel;
private Button retry;
private Button finish;
private View animBox;
private LottieAnimationView anim;
private boolean reduceMotion;

private final ImportSession session = ContentImportService.session();

Expand All @@ -52,6 +58,7 @@ public class ImportProgressFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle s) {
View root = inflater.inflate(R.layout.fragment_k2go_content_import, container, false);
title = root.findViewById(R.id.k2go_cxi_title);
source = root.findViewById(R.id.k2go_cxi_source);
overallLabel = root.findViewById(R.id.k2go_cxi_overall_label);
overallBar = root.findViewById(R.id.k2go_cxi_overall_bar);
Expand All @@ -63,6 +70,20 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
retry = root.findViewById(R.id.k2go_cxi_retry);
finish = root.findViewById(R.id.k2go_cxi_finish);

// Two-phones transfer animation, shared with Clone (R.raw.k2go_clone_loop). Reduce motion holds
// a single frame instead of looping; the screen never depends on movement to be understood.
animBox = root.findViewById(R.id.k2go_cxi_anim_box);
anim = root.findViewById(R.id.k2go_cxi_anim);
reduceMotion = Motion.reduced(getContext());
if (anim != null) {
anim.setAnimation(R.raw.k2go_clone_loop);
if (reduceMotion) {
anim.setProgress(0.5f);
} else {
anim.setRepeatCount(com.airbnb.lottie.LottieDrawable.INFINITE);
}
}

cancel.setOnClickListener(v -> ContentImportService.cancel(requireContext()));
retry.setOnClickListener(v -> ContentImportService.retry(requireContext()));
finish.setOnClickListener(v -> {
Expand All @@ -87,6 +108,9 @@ public void onResume() {
public void onPause() {
super.onPause();
session.setListener(null);
if (anim != null && anim.isAnimating()) {
anim.pauseAnimation();
}
}

private void render() {
Expand All @@ -95,6 +119,9 @@ private void render() {
goBack();
return;
}
// A Wi-Fi receive reuses this import screen; title it "Receive content" so the receive flow does
// not read "Import content" (K2GO-269 wording).
title.setText(session.isNetwork() ? R.string.k2go_cx_receive_title : R.string.k2go_cx_import_title);
source.setText(getString(R.string.k2go_cx_import_source_fmt, session.sourceLabel()));

boolean running = session.isRunning();
Expand All @@ -112,6 +139,24 @@ private void render() {
boolean settled = !running;
retry.setVisibility(settled && session.canRetry() ? View.VISIBLE : View.GONE);
finish.setVisibility(settled ? View.VISIBLE : View.GONE);
renderAnim(running);
}

/** Show the transfer animation only while the transfer is in flight; play it unless motion is off. */
private void renderAnim(boolean active) {
if (animBox != null) {
animBox.setVisibility(active ? View.VISIBLE : View.GONE);
}
if (anim == null) {
return;
}
if (active && !reduceMotion) {
if (!anim.isAnimating()) {
anim.playAnimation();
}
} else if (anim.isAnimating()) {
anim.pauseAnimation();
}
}

private void renderCurrentItem(boolean running) {
Expand Down
Loading
Loading