Support scan code and push

pull/21/head
FongMi 4 years ago
parent 07bc3d9842
commit 6c3631cca7
  1. 2
      app/build.gradle
  2. 2
      app/src/leanback/java/com/fongmi/android/tv/ui/activity/DetailActivity.java
  3. 9
      app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java
  4. 31
      app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java
  5. 94
      app/src/leanback/java/com/fongmi/android/tv/ui/custom/ConfigDialog.java
  6. 63
      app/src/leanback/res/layout/dialog_config.xml
  7. 36
      app/src/main/java/com/fongmi/android/tv/event/ServerEvent.java
  8. 93
      app/src/main/java/com/fongmi/android/tv/server/Nano.java
  9. 37
      app/src/main/java/com/fongmi/android/tv/server/Server.java
  10. 39
      app/src/main/java/com/fongmi/android/tv/server/process/InputRequestProcess.java
  11. 37
      app/src/main/java/com/fongmi/android/tv/server/process/RawRequestProcess.java
  12. 10
      app/src/main/java/com/fongmi/android/tv/server/process/RequestProcess.java
  13. 9
      app/src/main/java/com/fongmi/android/tv/utils/Notify.java
  14. 43
      app/src/main/java/com/fongmi/android/tv/utils/QRCode.java
  15. 110
      app/src/main/res/raw/index.html
  16. 4
      app/src/main/res/raw/jquery.js
  17. 42
      app/src/main/res/raw/script.js
  18. 5576
      app/src/main/res/raw/style.css
  19. 26
      app/src/main/res/raw/ui.css
  20. 2
      app/src/main/res/values-zh-rCN/strings.xml
  21. 2
      app/src/main/res/values-zh-rTW/strings.xml

@ -62,10 +62,10 @@ dependencies {
implementation 'com.google.android.exoplayer:exoplayer:2.18.1'
implementation 'com.google.android.exoplayer:extension-rtmp:2.18.1'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.google.zxing:core:3.5.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.10.0'
implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation('org.simpleframework:simple-xml:2.7.1') { exclude group: 'stax', module: 'stax-api' exclude group: 'xpp3', module: 'xpp3' }
leanbackImplementation 'androidx.leanback:leanback:1.2.0-alpha02'

@ -305,7 +305,7 @@ public class DetailActivity extends BaseActivity implements KeyDown.Listener {
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onPlaybackStateChanged(PlayerEvent event) {
public void onPlayerEvent(PlayerEvent event) {
mBinding.progress.getRoot().setVisibility(event.getState() == Player.STATE_BUFFERING ? View.VISIBLE : View.GONE);
if (event.getState() == Player.STATE_ENDED) onNext();
Notify.show(event.getMsg());

@ -25,6 +25,7 @@ import com.fongmi.android.tv.bean.Vod;
import com.fongmi.android.tv.databinding.ActivityHomeBinding;
import com.fongmi.android.tv.db.AppDatabase;
import com.fongmi.android.tv.event.RefreshEvent;
import com.fongmi.android.tv.event.ServerEvent;
import com.fongmi.android.tv.model.SiteViewModel;
import com.fongmi.android.tv.player.Players;
import com.fongmi.android.tv.server.Server;
@ -207,7 +208,7 @@ public class HomeActivity extends BaseActivity implements VodPresenter.OnClickLi
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onAdapterChanged(RefreshEvent event) {
public void onRefreshEvent(RefreshEvent event) {
if (event.getType() == RefreshEvent.Type.VIDEO) {
getVideo();
} else if (event.getType() == RefreshEvent.Type.IMAGE) {
@ -217,6 +218,12 @@ public class HomeActivity extends BaseActivity implements VodPresenter.OnClickLi
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onServerEvent(ServerEvent event) {
if (event.getType() != ServerEvent.Type.PUSH || ApiConfig.get().getSite("push_agent") == null) return;
DetailActivity.start(this, "push_agent", event.getText());
}
@Override
public void onBackPressed() {
if (mHistoryPresenter.isDelete()) {

@ -2,7 +2,6 @@ package com.fongmi.android.tv.ui.activity;
import android.Manifest;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
@ -10,11 +9,9 @@ import android.os.Environment;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.ItemBridgeAdapter;
@ -24,11 +21,11 @@ import com.fongmi.android.tv.R;
import com.fongmi.android.tv.api.ApiConfig;
import com.fongmi.android.tv.bean.Site;
import com.fongmi.android.tv.databinding.ActivitySettingBinding;
import com.fongmi.android.tv.databinding.DialogConfigBinding;
import com.fongmi.android.tv.databinding.DialogSiteBinding;
import com.fongmi.android.tv.db.AppDatabase;
import com.fongmi.android.tv.event.RefreshEvent;
import com.fongmi.android.tv.net.Callback;
import com.fongmi.android.tv.ui.custom.ConfigDialog;
import com.fongmi.android.tv.ui.presenter.SitePresenter;
import com.fongmi.android.tv.utils.Notify;
import com.fongmi.android.tv.utils.Prefers;
@ -36,7 +33,7 @@ import com.fongmi.android.tv.utils.ResUtil;
import org.greenrobot.eventbus.EventBus;
public class SettingActivity extends BaseActivity {
public class SettingActivity extends BaseActivity implements ConfigDialog.Callback {
private ActivitySettingBinding mBinding;
@ -62,26 +59,16 @@ public class SettingActivity extends BaseActivity {
@Override
protected void initEvent() {
mBinding.site.setOnClickListener(this::showSite);
mBinding.config.setOnClickListener(this::showConfig);
mBinding.config.setOnClickListener(view -> ConfigDialog.show(this));
mBinding.thumbnail.setOnClickListener(this::setThumbnail);
}
private void showConfig(View view) {
DialogConfigBinding bindingDialog = DialogConfigBinding.inflate(LayoutInflater.from(this));
bindingDialog.text.setText(Prefers.getUrl());
bindingDialog.text.setSelection(bindingDialog.text.getText().length());
AlertDialog dialog = Notify.show(this, bindingDialog.getRoot(), (dialogInterface, i) -> {
if (bindingDialog.text.getText().toString().equals(Prefers.getUrl())) return;
Prefers.putUrl(bindingDialog.text.getText().toString().trim());
mBinding.url.setText(Prefers.getUrl());
Notify.progress(this);
AppDatabase.clear();
checkUrl();
});
bindingDialog.text.setOnEditorActionListener((textView, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
return true;
});
@Override
public void setConfig() {
mBinding.url.setText(Prefers.getUrl());
Notify.progress(this);
AppDatabase.clear();
checkUrl();
}
private void checkUrl() {

@ -0,0 +1,94 @@
package com.fongmi.android.tv.ui.custom;
import android.app.Activity;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import androidx.appcompat.app.AlertDialog;
import com.fongmi.android.tv.databinding.DialogConfigBinding;
import com.fongmi.android.tv.event.ServerEvent;
import com.fongmi.android.tv.server.Server;
import com.fongmi.android.tv.utils.Prefers;
import com.fongmi.android.tv.utils.QRCode;
import com.fongmi.android.tv.utils.ResUtil;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class ConfigDialog implements DialogInterface.OnDismissListener {
private DialogConfigBinding binding;
private AlertDialog dialog;
private Callback callback;
public static void show(Activity activity) {
new ConfigDialog().create(activity);
}
public void create(Activity activity) {
callback = (Callback) activity;
binding = DialogConfigBinding.inflate(LayoutInflater.from(activity));
dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create();
EventBus.getDefault().register(this);
initDialog();
initView();
initEvent();
}
private void initDialog() {
WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
params.width = (int) (ResUtil.getScreenWidthPx() * 0.65f);
dialog.getWindow().setAttributes(params);
dialog.getWindow().setDimAmount(0);
dialog.setOnDismissListener(this);
dialog.show();
}
private void initView() {
binding.text.setText(Prefers.getUrl());
binding.text.setSelection(binding.text.getText().length());
binding.code.setImageBitmap(QRCode.getBitmap(Server.get().getAddress(false), 200));
}
private void initEvent() {
binding.positive.setOnClickListener(this::onPositive);
binding.negative.setOnClickListener(this::onNegative);
binding.text.setOnEditorActionListener((textView, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) binding.positive.performClick();
return true;
});
}
private void onPositive(View view) {
Prefers.putUrl(binding.text.getText().toString().trim());
callback.setConfig();
dialog.dismiss();
}
private void onNegative(View view) {
dialog.dismiss();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onServerEvent(ServerEvent event) {
if (event.getType() != ServerEvent.Type.API) return;
binding.text.setText(event.getText());
binding.text.setSelection(binding.text.getText().length());
}
@Override
public void onDismiss(DialogInterface dialogInterface) {
EventBus.getDefault().unregister(this);
}
public interface Callback {
void setConfig();
}
}

@ -1,20 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp">
<ImageView
android:id="@+id/code"
android:layout_width="150dp"
android:layout_height="150dp"
android:scaleType="fitXY" />
<EditText
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_toEndOf="@+id/code"
android:hint="@string/dialog_config_url_hint"
android:imeOptions="actionDone"
android:importantForAutofill="no"
android:inputType="text"
android:singleLine="true"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignStart="@+id/text"
android:layout_alignBottom="@+id/code"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/history"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/grey_700"
android:singleLine="true"
android:text="@string/setting_history"
android:textColor="@color/white" />
<Button
android:id="@+id/negative"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:backgroundTint="@color/grey_700"
android:singleLine="true"
android:text="@string/dialog_negative"
android:textColor="@color/white" />
<Button
android:id="@+id/positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/grey_700"
android:singleLine="true"
android:text="@string/dialog_positive"
android:textColor="@color/white" />
</LinearLayout>
</RelativeLayout>

@ -0,0 +1,36 @@
package com.fongmi.android.tv.event;
public class ServerEvent {
private final String text;
private final Type type;
public static ServerEvent search(String text) {
return new ServerEvent(Type.SEARCH, text);
}
public static ServerEvent push(String text) {
return new ServerEvent(Type.PUSH, text);
}
public static ServerEvent api(String text) {
return new ServerEvent(Type.API, text);
}
public ServerEvent(Type type, String text) {
this.type = type;
this.text = text;
}
public Type getType() {
return type;
}
public String getText() {
return text;
}
public enum Type {
SEARCH, PUSH, API
}
}

@ -1,58 +1,103 @@
package com.fongmi.android.tv.server;
import com.fongmi.android.tv.R;
import com.fongmi.android.tv.api.ApiConfig;
import com.fongmi.android.tv.server.process.InputRequestProcess;
import com.fongmi.android.tv.server.process.RawRequestProcess;
import com.fongmi.android.tv.server.process.RequestProcess;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import fi.iki.elonen.NanoHTTPD;
public class Nano extends NanoHTTPD {
private Listener mListener;
private List<RequestProcess> processes;
private Listener listener;
public Nano() {
super(9978);
addRequestProcess();
}
private void addRequestProcess() {
processes = new ArrayList<>();
processes.add(new InputRequestProcess(this));
processes.add(new RawRequestProcess("/", R.raw.index, NanoHTTPD.MIME_HTML));
processes.add(new RawRequestProcess("/index.html", R.raw.index, NanoHTTPD.MIME_HTML));
processes.add(new RawRequestProcess("/ui.css", R.raw.ui, "text/css"));
processes.add(new RawRequestProcess("/style.css", R.raw.style, "text/css"));
processes.add(new RawRequestProcess("/jquery.js", R.raw.jquery, "application/x-javascript"));
processes.add(new RawRequestProcess("/script.js", R.raw.script, "application/x-javascript"));
processes.add(new RawRequestProcess("/favicon.ico", R.mipmap.ic_launcher, "image/x-icon"));
}
public Listener getListener() {
return listener;
}
public void setListener(Listener listener) {
this.listener = listener;
}
@Override
public Response serve(IHTTPSession session) {
if (session.getUri().isEmpty()) return super.serve(session);
String url = session.getUri().trim();
if (url.indexOf('?') >= 0) url = url.substring(0, url.indexOf('?'));
if (session.getMethod() == Method.GET) {
if (url.equals("/proxy")) {
Map<String, String> params = session.getParms();
if (params.containsKey("do")) {
Object[] rs = ApiConfig.get().proxyLocal(params);
try {
int code = (int) rs[0];
String mime = (String) rs[1];
InputStream stream = rs[2] != null ? (InputStream) rs[2] : null;
return NanoHTTPD.newChunkedResponse(Response.Status.lookup(code), mime, stream);
} catch (Exception e) {
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500");
}
if (url.contains("?")) url = url.substring(0, url.indexOf('?'));
if (session.getMethod() == Method.POST) parseBody(session);
for (RequestProcess process : processes) {
if (process.isRequest(session, url)) {
return process.doResponse(session, url);
}
}
if (session.getMethod() == Method.GET && url.equals("/proxy")) {
Map<String, String> params = session.getParms();
if (params.containsKey("do")) {
Object[] rs = ApiConfig.get().proxyLocal(params);
try {
int code = (int) rs[0];
String mime = (String) rs[1];
InputStream stream = rs[2] != null ? (InputStream) rs[2] : null;
return NanoHTTPD.newChunkedResponse(Response.Status.lookup(code), mime, stream);
} catch (Exception e) {
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500");
}
}
}
return super.serve(session);
return processes.get(0).doResponse(session, "");
}
public Listener getListener() {
return mListener;
private void parseBody(IHTTPSession session) {
Map<String, String> files = new HashMap<>();
try {
String hd = session.getHeaders().get("content-type");
if (hd == null) return;
if (hd.toLowerCase().contains("multipart/form-data") && !hd.toLowerCase().contains("charset=")) {
Matcher matcher = Pattern.compile("[ |\t]*(boundary[ |\t]*=[ |\t]*['|\"]?[^\"^'^;^,]*['|\"]?)", Pattern.CASE_INSENSITIVE).matcher(hd);
String boundary = matcher.find() ? matcher.group(1) : null;
if (boundary != null) session.getHeaders().put("content-type", "multipart/form-data; charset=utf-8; " + boundary);
}
session.parseBody(files);
} catch (Exception ignored) {
}
}
public void setListener(Listener listener) {
this.mListener = listener;
public static Response createPlainTextResponse(Response.IStatus status, String text) {
return newFixedLengthResponse(status, NanoHTTPD.MIME_PLAINTEXT, text);
}
public interface Listener {
void onTextReceived(String text);
void onSearch(String text);
void onApiReceived(String url);
void onPush(String url);
void onPushReceived(String url);
void onApi(String url);
}
}

@ -1,10 +1,13 @@
package com.fongmi.android.tv.server;
import com.fongmi.android.tv.event.ServerEvent;
import com.fongmi.android.tv.utils.Utils;
import org.greenrobot.eventbus.EventBus;
public class Server implements Nano.Listener {
private Nano mNano;
private Nano nano;
private static class Loader {
static volatile Server INSTANCE = new Server();
@ -15,40 +18,40 @@ public class Server implements Nano.Listener {
}
public String getAddress(boolean local) {
return "http://" + (local ? "127.0.0.1" : Utils.getIP()) + ":" + mNano.getListeningPort() + "/";
return "http://" + (local ? "127.0.0.1" : Utils.getIP()) + ":" + nano.getListeningPort() + "/";
}
public void start() {
if (mNano != null) return;
if (nano != null) return;
try {
mNano = new Nano();
mNano.setListener(this);
mNano.start();
nano = new Nano();
nano.setListener(this);
nano.start();
} catch (Exception e) {
mNano.stop();
mNano = null;
nano.stop();
nano = null;
}
}
public void stop() {
if (mNano != null) {
mNano.stop();
mNano = null;
if (nano != null) {
nano.stop();
nano = null;
}
}
@Override
public void onTextReceived(String text) {
public void onSearch(String text) {
EventBus.getDefault().post(ServerEvent.search(text));
}
@Override
public void onApiReceived(String url) {
public void onPush(String url) {
EventBus.getDefault().post(ServerEvent.push(url));
}
@Override
public void onPushReceived(String url) {
public void onApi(String url) {
EventBus.getDefault().post(ServerEvent.api(url));
}
}

@ -0,0 +1,39 @@
package com.fongmi.android.tv.server.process;
import com.fongmi.android.tv.server.Nano;
import java.util.Map;
import fi.iki.elonen.NanoHTTPD;
public class InputRequestProcess implements RequestProcess {
private final Nano nano;
public InputRequestProcess(Nano nano) {
this.nano = nano;
}
@Override
public boolean isRequest(NanoHTTPD.IHTTPSession session, String path) {
return session.getMethod() == NanoHTTPD.Method.POST && path.equals("/action");
}
@Override
public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path) {
if (!path.equals("/action")) return Nano.createPlainTextResponse(NanoHTTPD.Response.Status.NOT_FOUND, "Error 404, file not found.");
Map<String, String> params = session.getParms();
switch (params.get("do")) {
case "search":
nano.getListener().onSearch(params.get("word").trim());
break;
case "push":
nano.getListener().onPush(params.get("url").trim());
break;
case "api":
nano.getListener().onApi(params.get("url").trim());
break;
}
return Nano.createPlainTextResponse(NanoHTTPD.Response.Status.OK, "ok");
}
}

@ -0,0 +1,37 @@
package com.fongmi.android.tv.server.process;
import com.fongmi.android.tv.App;
import com.fongmi.android.tv.server.Nano;
import java.io.IOException;
import java.io.InputStream;
import fi.iki.elonen.NanoHTTPD;
public class RawRequestProcess implements RequestProcess {
private final String mimeType;
private final String path;
private final int resId;
public RawRequestProcess(String path, int resId, String mimeType) {
this.path = path;
this.resId = resId;
this.mimeType = mimeType;
}
@Override
public boolean isRequest(NanoHTTPD.IHTTPSession session, String path) {
return session.getMethod() == NanoHTTPD.Method.GET && path.equalsIgnoreCase(this.path);
}
@Override
public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path) {
try {
InputStream is = App.get().getResources().openRawResource(resId);
return Nano.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, mimeType + ";charset=utf-8", is, is.available());
} catch (IOException IOExc) {
return Nano.createPlainTextResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + IOExc.getMessage());
}
}
}

@ -0,0 +1,10 @@
package com.fongmi.android.tv.server.process;
import fi.iki.elonen.NanoHTTPD;
public interface RequestProcess {
boolean isRequest(NanoHTTPD.IHTTPSession session, String path);
NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path);
}

@ -1,7 +1,6 @@
package com.fongmi.android.tv.utils;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
@ -12,7 +11,6 @@ import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.fongmi.android.tv.App;
import com.fongmi.android.tv.R;
import com.fongmi.android.tv.databinding.ViewProgressBinding;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@ -47,13 +45,6 @@ public class Notify {
get().mDialog.show();
}
public static AlertDialog show(Context context, View view, DialogInterface.OnClickListener listener) {
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setView(view).setNegativeButton(R.string.dialog_negative, null).setPositiveButton(R.string.dialog_positive, listener).create();
dialog.getWindow().setDimAmount(0);
dialog.show();
return dialog;
}
public static void progress(Context context) {
ViewProgressBinding binding = ViewProgressBinding.inflate(LayoutInflater.from(context));
get().mDialog = new MaterialAlertDialogBuilder(context).setView(binding.getRoot()).create();

@ -0,0 +1,43 @@
package com.fongmi.android.tv.utils;
import android.graphics.Bitmap;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import java.util.EnumMap;
import java.util.Map;
public class QRCode {
private static final int WHITE = 0xFFFFFFFF;
private static final int BLACK = 0xFF000000;
public static Bitmap createBitmap(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = matrix.get(x, y) ? BLACK : WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
public static Bitmap getBitmap(String contents, int size) {
try {
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 0);
return createBitmap(new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, ResUtil.dp2px(size), ResUtil.dp2px(size), hints));
} catch (Exception e) {
return null;
}
}
}

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
<meta name="wechat-enable-text-zoom-em" content="true">
<title>TV</title>
<meta name="layoutmode" content="standard">
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="ui.css">
</head>
<body>
<div class="page">
<div class="page__bd" style="height: 100%;">
<div class="weui-tab">
<div id="panel1" role="tabpanel" aria-labelledby="tab1" class="weui-tab__panel">
<div class="weui-form">
<div class="weui-form__text-area">
<h2 class="weui-form__title">搜尋</h2>
</div>
<div class="weui-form__control-area">
<div class="weui-cells__group weui-cells__group_form">
<div class="weui-cells">
<div class="weui-cell weui-cell_active weui-cell_vcode weui-cell_wrap">
<div class="weui-cell__bd weui-flex">
<input id="search_key_word" class="weui-input weui-cell__control weui-cell__control_flex" type="text" value="" placeholder="請輸入搜尋的關鍵字..." />
<button onclick="search(); return false;" class="weui-cell__control weui-btn weui-btn_default weui-vcode-btn">搜尋</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="panel2" role="tabpanel" aria-labelledby="tab2" class="weui-tab__panel" style="display: none;">
<div class="weui-form">
<div class="weui-form__text-area">
<h2 class="weui-form__title">推送</h2>
</div>
<div class="weui-form__control-area">
<div class="weui-cells__group weui-cells__group_form">
<div class="weui-cells">
<div class="weui-cell weui-cell_active weui-cell_vcode weui-cell_wrap">
<div class="weui-cell__bd weui-flex">
<input id="push_url" class="weui-input weui-cell__control weui-cell__control_flex" type="text" value="" placeholder="請輸入需要推送播放的地址..." />
<button onclick="push(); return false;" class="weui-cell__control weui-btn weui-btn_default weui-vcode-btn">推送</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="panel3" role="tabpanel" aria-labelledby="tab3" class="weui-tab__panel" style="display: none;">
<div class="weui-form">
<div class="weui-form__text-area">
<h2 class="weui-form__title">接口</h2>
</div>
<div class="weui-form__control-area">
<div class="weui-cells__group weui-cells__group_form">
<div class="weui-cells">
<div class="weui-cell weui-cell_active weui-cell_vcode weui-cell_wrap">
<div class="weui-cell__bd weui-flex">
<input id="diy_api_url" class="weui-input weui-cell__control weui-cell__control_flex" type="text" value="" placeholder="請輸入自定義配置接口地址..." />
<button onclick="api(); return false;" class="weui-cell__control weui-btn weui-btn_default weui-vcode-btn">確定</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div role="tablist" class="weui-tabbar">
<div id="tab1" role="tab" aria-labelledby="t1_title" aria-selected="true" aria-controls="panel1" class="weui-tabbar__item weui-bar__item_on">
<img style="width: 20px; height: 20px; margin: 5px 0 5px 0;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAuQAAALkB4qdB6AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAKfSURBVFiFxdfPi1ZVGMDxz31HCnOq0ZkK00THlxhpIToELdxILkQMRMN1bly1EAlxY/0BswqiFi7KnW3aBSJFKTg2iJY/CsIfEZqjmSJiipKdFvfR9/Jy7zv3bd55PXA4l3vO8zzf85zznPOcLKWkU8mybCE2YhPGsATDuIGr+Blf43BK6U5HZWUlpVRaMQ/78ACpRr2H3WhU6SyrWZkHsixbjc+xJpSfwUmcivZCeGMtxqO+EeLHsCOldL5rD2AAH+FhGP4Nb9eZCd7BHwVv7Kol16bkk1DwLz7FYDfuxBC+KCzLh7UBsLMguLUbwyUgOwoT2T4jABbjbgh9NhvjBYiDoe8vDM0EsD8GX8CCHgEskodpwkSHcYbxT7hrXS+MF5RvCoC/8VwVwHsx6JdeGi8YmA79W8r6G9gcEXmiVtx2X05Gu7mss4FmnwCaZZ0N+dneD4AlZZ0NDMb39TkCeKz3mSqA6fheO0cAa6I9XQVwJb7fnCOA8WhPVQFMPk0AeEsep7cxv8dnwItaN+trVQdRhvMx6OMeAxwIvd/OdBe8q3V7re+R8S1auUGzI0AIHAmB3/HCLI2/JA+/hL0dxxaEFssjIuEoVvxP42PyQy3JQ29eLYAQHsefIXwX75PnjTUMD2AP7mslNsfxfG2Agvu+LCj5PtazaheviD00VZCZLExkstOSdprRtsI6Pq43cEieO36Dm239l0QKJs+Sr8X/KRVZ0UxuHcEEfm0zVKz38SM+wLNt8qu08oETWNhuo/RdUFayLFup9TIakqdv53AxpfSog9zr+A6vBuiGlNKtJwNmE25dREYTl8MTP2Gk1hL0GGJUfsYknMXLfQUIiOXy11aSP2pfqb0HelWyLFsm3xOj+KHvAAGxFF/h8n+GU+2I7XnhZwAAAABJRU5ErkJggg==" alt="" class="weui-tabbar__icon">
<p id="t1_title" aria-hidden="true" class="weui-tabbar__label">搜尋</p>
</div>
<div id="tab2" role="tab" aria-labelledby="t2_title" aria-selected="false" aria-controls="panel2" class="weui-tabbar__item">
<img style="width: 20px; height: 20px; margin: 5px 0 5px 0;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAuQAAALkB4qdB6AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAJlSURBVFiF7ddNSNRBGMfxzy6B4aUsoxfMXiQwO1kEHewQXUI6FXWqIIzQgii69XILOnToFlYE3SMSkiCJolsW7CUIen+j6FJBiGWm02FHG9fddZdELw48/Hdmfs/zfGfmYZjNhBDMZsvOavY5AMyrRpzJZLagHRuj1eAFnqI7hJCrmiCEMKVhIa5gFKGM9aGhkpjjsStI3orPMcEPXMQeNKAeW3Ea36LmEzZNCwAW4FUM/BItZbR1uBm1X7F8OgBuxIDPUFfBbtXgYfS5/V8A2JycbXvFW8pajCS71o29qK8W4FQMcr/EfAOeIIfVBXM9RQp0AIeqAbgfHSc7sTKpjYB3WJPMz0MzduMs3ibam1hUEgDz0Ymh6NBWJPnrIit8n0IU+NTiPH4X7mqhsANfCgLXJ/ONSfJconmUQKwts6ttCUTHBACcSwL24WB6tliFN0nyRYl+QQLxAU1lIE5E3XcsGxs8GQcH0VnEaVVyjrmxMxwDiL9TiI9TQIzV11FYKn/DBewv4fCkMHkhQBGIp2UAuqLmGlyInZ4yDpfRmyYvBpBA3ML1MvGakgV5EDu7SjmUCTQJoEK/xdH3RxYb5Fu/mWvN8fssi+HYGZxBgG3x25+VLzBYNxOZM5lMLY7F7t0sHsfOvpkAwHEswb0Qwh1YIf+YGMSOKoupF71V6Lfjj/yxt4zfhHH1IU4crraqK0zeKv9QCTgyPp4ILvl3teZwBuunKXkHfsbYJybMFQh3+vf+m24bQNckuCK0C3EAV/Ecv6INRfsdbTixP9FGoo2a+ILuQWOx3cnEpLPWZv2f0RzAX4u0r6Ikm0C7AAAAAElFTkSuQmCC" alt="" class="weui-tabbar__icon">
<p aria-hidden="true" id="t2_title" class="weui-tabbar__label">推送</p>
</div>
<div id="tab3" role="tab" aria-labelledby="t3_title" aria-selected="false" aria-controls="panel3" class="weui-tabbar__item">
<img style="width: 20px; height: 20px; margin: 5px 0 5px 0;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAC5AAAAuQHip0HoAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAANtQTFRF////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhQHXOQAAAEh0Uk5TAAECBAUGCQoNDxITGiAhKC4xMjM0Nzg8P0BDT1dmbHR4fX+MlpedoKOkp6issri5vL2/zs/S19zd4OHi5OXp7e/x8/f6+/z+bkNkuAAAATRJREFUOMuNk2kzAmAUhR8iUSHSokWobCFLtFBI9fz/X+RDzdQY9brfzl3mbufAghVfVF+KLLHoh6p+RJckHGkxmSzq0ZKEjF2ArplfgcPjHYBC2w5Ax3YBYOf4cBovjfw82dy71/ElwOVY7/c2Tz4dlQB2h6q9b3uVxLQiUen53VMd7gINWwflgT7G503jjzoop55sQN5JBuK12sbiWBu1WhzSE/PcectSu/WOjs31uWcrm92ao/WmHc70OTtzbNfHOq5vz2D2Wc9Yq3zpBQCxV+339TUGwIV+VdaAxIMtAKq+5SD3bhWAlg+zvc+9BogMzQHkHEYAbjyf9bryFGDf/hT33Qc49eq/CaEWgSGDawYPFTx18Fl5J+mV76bhU2oVYcKUC5I2TPuwcP4tvaB4/5T/D6drVhrBHvH6AAAAAElFTkSuQmCC" alt="" class="weui-tabbar__icon">
<p id="t3_title" aria-hidden="true" class="weui-tabbar__label">接口</p>
</div>
</div>
</div>
</div>
</div>
<div role="alert" id="warnToast" style="display: none;">
<div class="weui-mask_transparent"></div>
<div class="weui-toast">
<i class="weui-icon-warn weui-icon_toast"></i>
<p class="weui-toast__content" id="warnToastContent"></p>
</div>
</div>
<div role="alert" id="loadingToast" style="display: none;">
<div class="weui-mask_transparent"></div>
<div class="weui-toast">
<span class="weui-primary-loading weui-icon_toast"><span class="weui-primary-loading__dot"></span></span>
<p class="weui-toast__content">載入中</p>
</div>
</div>
<script src="jquery.js"></script>
<script src="script.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

@ -0,0 +1,42 @@
function search() {
doAction('search', {word: $('#search_key_word').val()});
}
function api() {
doAction('api', {url: $('#diy_api_url').val()});
}
function push() {
doAction('push', {url: $('#push_url').val()});
}
function doAction(action, kv) {
kv['do'] = action;
$.post('/action', kv, function (data) {
console.log(data);
});
return false;
}
function warnToast(msg) {
$('#warnToastContent').html(msg);
$('#warnToast').show();
setTimeout(() => {
$('#warnToast').hide();
}, 1000);
}
function showPanel(id) {
let tab = $('#tab' + id)[0];
$(tab).attr('aria-selected', 'true').addClass('weui-bar__item_on');
$(tab).siblings('.weui-bar__item_on').removeClass('weui-bar__item_on').attr('aria-selected', 'false');
var panelId = '#' + $(tab).attr('aria-controls');
$(panelId).css('display', 'block');
$(panelId).siblings('.weui-tab__panel').css('display', 'none');
}
$(function () {
$('.weui-tabbar__item').on('click', function () {
showPanel(parseInt($(this).attr('id').substr(3)));
});
});

File diff suppressed because it is too large Load Diff

@ -0,0 +1,26 @@
body,
html {
height: 100%;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: system-ui, -apple-system, Helvetica Neue, sans-serif;
}
.page,
body {
background-color: var(--weui-BG-0);
}
.page {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
z-index: 1;
}

@ -40,7 +40,7 @@
<!-- Setting -->
<string name="setting_site">首页</string>
<string name="setting_url">配置</string>
<string name="setting_history">历史纪录</string>
<string name="setting_history">历史</string>
<string name="setting_thumbnail">缩图</string>
<!-- Dialog -->

@ -40,7 +40,7 @@
<!-- Setting -->
<string name="setting_site">首頁</string>
<string name="setting_url">配置</string>
<string name="setting_history">歷史紀錄</string>
<string name="setting_history">歷史</string>
<string name="setting_thumbnail">縮圖</string>
<!-- Dialog -->

Loading…
Cancel
Save