From 9a739f325889a551ac6fb45956f6d74bb2d9f9d4 Mon Sep 17 00:00:00 2001 From: jade Date: Wed, 29 May 2024 10:02:18 +0800 Subject: [PATCH 1/5] TV Support Scan Devices And Sync Keep and History --- .../tv/ui/activity/SettingActivity.java | 11 ++ .../android/tv/ui/adapter/DeviceAdapter.java | 92 ++++++++++ .../fongmi/android/tv/ui/cast/ScanEvent.java | 20 +++ .../fongmi/android/tv/ui/cast/ScanTask.java | 81 +++++++++ .../android/tv/ui/dialog/SyncDialog.java | 162 ++++++++++++++++++ .../res/drawable/ic_action_refresh.xml | 10 ++ app/src/leanback/res/drawable/ic_cast_tv.xml | 10 ++ .../leanback/res/drawable/shape_accent.xml | 23 +++ .../leanback/res/layout/activity_setting.xml | 47 +++++ .../leanback/res/layout/adapter_device.xml | 43 +++++ app/src/leanback/res/layout/dialog_device.xml | 47 +++++ app/src/main/res/values-zh-rCN/strings.xml | 6 + app/src/main/res/values-zh-rTW/strings.xml | 6 + app/src/main/res/values/strings.xml | 6 + 14 files changed, 564 insertions(+) create mode 100644 app/src/leanback/java/com/fongmi/android/tv/ui/adapter/DeviceAdapter.java create mode 100644 app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanEvent.java create mode 100644 app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanTask.java create mode 100644 app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java create mode 100644 app/src/leanback/res/drawable/ic_action_refresh.xml create mode 100644 app/src/leanback/res/drawable/ic_cast_tv.xml create mode 100644 app/src/leanback/res/drawable/shape_accent.xml create mode 100644 app/src/leanback/res/layout/adapter_device.xml create mode 100644 app/src/leanback/res/layout/dialog_device.xml diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java index a99f141b12..b961ebcf34 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java @@ -33,6 +33,7 @@ import com.fongmi.android.tv.ui.dialog.LiveDialog; import com.fongmi.android.tv.ui.dialog.ProxyDialog; import com.fongmi.android.tv.ui.dialog.SiteDialog; +import com.fongmi.android.tv.ui.dialog.SyncDialog; import com.fongmi.android.tv.utils.FileUtil; import com.fongmi.android.tv.utils.Notify; import com.fongmi.android.tv.utils.UrlUtil; @@ -116,6 +117,8 @@ protected void initEvent() { mBinding.custom.setOnClickListener(this::onCustom); mBinding.doh.setOnClickListener(this::setDoh); mBinding.about.setOnClickListener(this::onAbout); + mBinding.syncKeep.setOnClickListener(this::onSyncKeep); + mBinding.syncHistory.setOnClickListener(this::onSyncHistory); } @Override @@ -285,6 +288,14 @@ private void onAbout(View view) { mBinding.aboutText.setText(BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_api + "-" + BuildConfig.FLAVOR_abi); } + private void onSyncKeep(View view){ + SyncDialog.create(this).keep().show(); + } + + private void onSyncHistory(View view){ + SyncDialog.create(this).history().show(); + } + private void setDoh(View view) { DohDialog.create(this).index(getDohIndex()).show(); } diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/DeviceAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/DeviceAdapter.java new file mode 100644 index 0000000000..977dec1a39 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/DeviceAdapter.java @@ -0,0 +1,92 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.Device; +import com.fongmi.android.tv.databinding.AdapterDeviceBinding; + +import java.util.ArrayList; +import java.util.List; + +public class DeviceAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public DeviceAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(Device item); + + boolean onLongClick(Device item); + } + + public void addAll(List items) { + if (items == null) return; + mItems.removeAll(items); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public void remove(Device item) { + if (item == null) return; + mItems.remove(item); + notifyDataSetChanged(); + } + + public void clear() { + mItems.clear(); + Device.delete(); + notifyDataSetChanged(); + } + + public List getIps() { + List ips = new ArrayList<>(); + for (Device item : mItems) if (item.isApp()) ips.add(item.getIp()); + return ips; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterDeviceBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Device item = mItems.get(position); + holder.binding.name.setText(item.getName()); + holder.binding.host.setText(item.getHost()); + holder.binding.type.setImageResource(getIcon(item)); + holder.binding.getRoot().setOnClickListener(v -> mListener.onItemClick(item)); + holder.binding.getRoot().setOnLongClickListener(v -> mListener.onLongClick(item)); + } + + private int getIcon(Device item) { + return item.isMobile() ? R.drawable.ic_cast_tv : R.drawable.ic_cast_tv; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterDeviceBinding binding; + + ViewHolder(@NonNull AdapterDeviceBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanEvent.java b/app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanEvent.java new file mode 100644 index 0000000000..8d22f78f80 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanEvent.java @@ -0,0 +1,20 @@ +package com.fongmi.android.tv.ui.cast; + +import org.greenrobot.eventbus.EventBus; + +public class ScanEvent { + + private final String address; + + public static void post(String address) { + EventBus.getDefault().post(new ScanEvent(address)); + } + + public ScanEvent(String address) { + this.address = address; + } + + public String getAddress() { + return address; + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanTask.java b/app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanTask.java new file mode 100644 index 0000000000..8ed81ee82e --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanTask.java @@ -0,0 +1,81 @@ +package com.fongmi.android.tv.ui.cast; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.bean.Device; +import com.fongmi.android.tv.server.Server; +import com.github.catvod.net.OkHttp; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import okhttp3.OkHttpClient; + +public class ScanTask { + + private final Listener listener; + private final OkHttpClient client; + private final List devices; + + public static ScanTask create(Listener listener) { + return new ScanTask(listener); + } + + public ScanTask(Listener listener) { + this.listener = listener; + this.client = OkHttp.client(1000); + this.devices = new ArrayList<>(); + } + + public void start(List ips) { + App.execute(() -> run(getUrl(ips))); + } + + public void start(String url) { + App.execute(() -> run(Arrays.asList(url))); + } + + private void run(List items) { + try { + getDevice(items); + } catch (Exception e) { + e.printStackTrace(); + } finally { + App.post(() -> listener.onFind(devices)); + } + } + + private void getDevice(List urls) throws Exception { + CountDownLatch cd = new CountDownLatch(urls.size()); + for (String url : urls) new Thread(() -> findDevice(cd, url)).start(); + cd.await(); + } + + private List getUrl(List ips) { + LinkedHashSet urls = new LinkedHashSet<>(ips); + String local = Server.get().getAddress(); + String base = local.substring(0, local.lastIndexOf(".") + 1); + for (int i = 1; i < 256; i++) urls.add(base + i + ":9978"); + return new ArrayList<>(urls); + } + + private void findDevice(CountDownLatch cd, String url) { + try { + if (url.contains(Server.get().getAddress())) return; + String result = OkHttp.newCall(client, url.concat("/device")).execute().body().string(); + Device device = Device.objectFrom(result); + if (device == null) return; + devices.add(device.save()); + } catch (Exception ignored) { + } finally { + cd.countDown(); + } + } + + public interface Listener { + + void onFind(List devices); + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java new file mode 100644 index 0000000000..aae6800639 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java @@ -0,0 +1,162 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Device; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.databinding.DialogDeviceBinding; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.adapter.DeviceAdapter; +import com.fongmi.android.tv.ui.cast.ScanEvent; +import com.fongmi.android.tv.ui.cast.ScanTask; +import com.fongmi.android.tv.utils.Notify; +import com.github.catvod.net.OkHttp; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.IOException; +import java.util.List; + +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Response; + +public class SyncDialog implements DeviceAdapter.OnClickListener, ScanTask.Listener { + + private final FormBody.Builder body; + private final OkHttpClient client; + private DialogDeviceBinding binding; + private DeviceAdapter adapter; + private String type; + + private final AlertDialog dialog; + + + public static SyncDialog create(FragmentActivity activity) { + return new SyncDialog(activity); + } + + public SyncDialog(FragmentActivity activity) { + client = OkHttp.client(Constant.TIMEOUT_SYNC); + this.binding = DialogDeviceBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + body = new FormBody.Builder(); + } + + private void initDialog() { + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + dialog.show(); + } + + public SyncDialog history() { + type = "history"; + body.add("device", Device.get().toString()); + body.add("targets", App.gson().toJson(History.get())); + if (VodConfig.getUrl() != null) body.add("url", VodConfig.getUrl()); + return this; + } + + public SyncDialog keep() { + type = "keep"; + body.add("device", Device.get().toString()); + body.add("targets", App.gson().toJson(Keep.getVod())); + body.add("configs", App.gson().toJson(Config.findUrls())); + return this; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + + protected void initView() { + EventBus.getDefault().register(this); + setRecyclerView(); + getDevice(); + } + + protected void initEvent() { + binding.refresh.setOnClickListener(v -> onRefresh()); + } + + private void setRecyclerView() { + binding.recycler.setHasFixedSize(true); + binding.recycler.setAdapter(adapter = new DeviceAdapter(this)); + } + + private void getDevice() { + adapter.addAll(Device.getAll()); + if (adapter.getItemCount() == 0) App.post(this::onRefresh, 1000); + } + + private void onRefresh() { + ScanTask.create(this).start(adapter.getIps()); + adapter.clear(); + } + + private void onSuccess() { + dismiss(); + } + + private void onError() { + Notify.show(R.string.device_offline); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onScanEvent(ScanEvent event) { + ScanTask.create(this).start(event.getAddress()); + } + + @Override + public void onFind(List devices) { + if (devices.size() > 0) adapter.addAll(devices); + } + + @Override + public void onItemClick(Device item) { + OkHttp.newCall(client, item.getIp().concat("/action?do=sync&mode=0&type=").concat(type), body.build()).enqueue(getCallback()); + } + + @Override + public boolean onLongClick(Device item) { + OkHttp.newCall(client, item.getIp().concat("/action?do=sync&mode=1&type=").concat(type), body.build()).enqueue(getCallback()); + return true; + } + + private Callback getCallback() { + return new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + App.post(() -> onSuccess()); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + App.post(() -> onError()); + } + }; + } + private void dismiss() { + try { + if (dialog != null) dialog.dismiss(); + } catch (Exception ignored) { + } + } +} \ No newline at end of file diff --git a/app/src/leanback/res/drawable/ic_action_refresh.xml b/app/src/leanback/res/drawable/ic_action_refresh.xml new file mode 100644 index 0000000000..f87979bba6 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_action_refresh.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/ic_cast_tv.xml b/app/src/leanback/res/drawable/ic_cast_tv.xml new file mode 100644 index 0000000000..836a90838d --- /dev/null +++ b/app/src/leanback/res/drawable/ic_cast_tv.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_accent.xml b/app/src/leanback/res/drawable/shape_accent.xml new file mode 100644 index 0000000000..aaf7996a83 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_accent.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_setting.xml b/app/src/leanback/res/layout/activity_setting.xml index ecbdaa19b9..7a61616692 100644 --- a/app/src/leanback/res/layout/activity_setting.xml +++ b/app/src/leanback/res/layout/activity_setting.xml @@ -451,6 +451,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_device.xml b/app/src/leanback/res/layout/dialog_device.xml new file mode 100644 index 0000000000..77a6f45efa --- /dev/null +++ b/app/src/leanback/res/layout/dialog_device.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b1a16ced51..2fdf95c6d5 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -98,6 +98,8 @@ 自动 版本 关于 + 收藏同步 + 历史同步 权限 关闭 开启 @@ -178,6 +180,10 @@ 已复制 接口已刷新 + + 选择装置 + 装置已离线 + 全部 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 5a1dd4df93..de8e993283 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -98,6 +98,8 @@ 自動 版本 關於 + 收藏同步 + 歷史同步 權限 關閉 開啟 @@ -179,6 +181,10 @@ 已複製 接口已刷新 + + 選擇裝置 + 裝置已離線 + 全部 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e00bf8a05..5cb31a8b2c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,6 +98,8 @@ Auto Version About + Keep + Sync History Permission Off On @@ -178,6 +180,10 @@ Copied Config rereshed + + Select Device + Device is offline + All times From 4febbf0750d0d303058364e6113feba23d301ee2 Mon Sep 17 00:00:00 2001 From: jade Date: Thu, 6 Jun 2024 16:46:29 +0800 Subject: [PATCH 2/5] =?UTF-8?q?*=20=E8=B0=83=E6=95=B4=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E6=94=B6=E8=97=8F=E5=B8=83=E5=B1=80=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=94=B6=E8=97=8F=E6=97=A0=E7=84=A6=E7=82=B9?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/tv/ui/dialog/SyncDialog.java | 4 ++ .../leanback/res/layout/activity_setting.xml | 44 +++++++++++-------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java index aae6800639..044e87039b 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java @@ -21,6 +21,7 @@ import com.fongmi.android.tv.ui.cast.ScanEvent; import com.fongmi.android.tv.ui.cast.ScanTask; import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; import com.github.catvod.net.OkHttp; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -60,6 +61,9 @@ public SyncDialog(FragmentActivity activity) { private void initDialog() { WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.4f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); dialog.show(); } diff --git a/app/src/leanback/res/layout/activity_setting.xml b/app/src/leanback/res/layout/activity_setting.xml index 4ac414e280..faf1940446 100644 --- a/app/src/leanback/res/layout/activity_setting.xml +++ b/app/src/leanback/res/layout/activity_setting.xml @@ -447,9 +447,8 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:orientation="horizontal"> - - - - + + + + + + + + From 74c96ec475344bdabdc8b2066e1d61b464c94585 Mon Sep 17 00:00:00 2001 From: jade Date: Thu, 6 Jun 2024 17:13:35 +0800 Subject: [PATCH 3/5] * Fix Conflict --- .../leanback/res/layout/activity_setting.xml | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/app/src/leanback/res/layout/activity_setting.xml b/app/src/leanback/res/layout/activity_setting.xml index bc6f7c5c83..f74644d26f 100644 --- a/app/src/leanback/res/layout/activity_setting.xml +++ b/app/src/leanback/res/layout/activity_setting.xml @@ -526,54 +526,6 @@ - - - - - - - - - - - - - - - Date: Thu, 6 Jun 2024 17:13:35 +0800 Subject: [PATCH 4/5] Fix Conflict --- .../leanback/res/layout/activity_setting.xml | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/app/src/leanback/res/layout/activity_setting.xml b/app/src/leanback/res/layout/activity_setting.xml index bc6f7c5c83..f74644d26f 100644 --- a/app/src/leanback/res/layout/activity_setting.xml +++ b/app/src/leanback/res/layout/activity_setting.xml @@ -526,54 +526,6 @@ - - - - - - - - - - - - - - - Date: Thu, 6 Jun 2024 18:25:48 +0800 Subject: [PATCH 5/5] * fix about UI --- app/src/leanback/res/layout/activity_setting.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/leanback/res/layout/activity_setting.xml b/app/src/leanback/res/layout/activity_setting.xml index f74644d26f..06055941f5 100644 --- a/app/src/leanback/res/layout/activity_setting.xml +++ b/app/src/leanback/res/layout/activity_setting.xml @@ -497,7 +497,6 @@ android:id="@+id/version" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" android:layout_weight="1" android:background="@drawable/selector_item" android:focusable="true"