From 394ca4513cd858eef50825088a7013300736bfe9 Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Thu, 14 Oct 2021 15:49:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=85=8D=E7=BD=AE=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD=EF=BC=88=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E5=85=8B=E9=9A=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 2 +- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 4 + .../idormy/sms/forwarder/CloneActivity.java | 238 ++++++++++++++++ .../idormy/sms/forwarder/MainActivity.java | 8 + .../idormy/sms/forwarder/utils/NetUtil.java | 27 +- .../idormy/sms/forwarder/view/IPEditText.java | 255 ++++++++++++++++++ .../main/res/drawable-mdpi/receive_btn.xml | 16 ++ app/src/main/res/drawable-mdpi/send_btn.xml | 16 ++ app/src/main/res/layout/activity_clone.xml | 119 ++++++++ app/src/main/res/layout/iptext.xml | 65 +++++ app/src/main/res/menu/menu_main.xml | 5 + app/src/main/res/values-en/colors.xml | 4 +- app/src/main/res/values-en/strings.xml | 6 + app/src/main/res/values/colors.xml | 4 +- app/src/main/res/values/strings.xml | 8 +- build.gradle | 2 +- version.properties | 4 +- 19 files changed, 775 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java create mode 100644 app/src/main/java/com/idormy/sms/forwarder/view/IPEditText.java create mode 100644 app/src/main/res/drawable-mdpi/receive_btn.xml create mode 100644 app/src/main/res/drawable-mdpi/send_btn.xml create mode 100644 app/src/main/res/layout/activity_clone.xml create mode 100644 app/src/main/res/layout/iptext.xml diff --git a/.gitignore b/.gitignore index 52a04b14..4018676e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ gradle.properties /keystore/keystore.properties /app/release /keystore +*.bak diff --git a/README.md b/README.md index ba954225..a699c260 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ -------- -## 更新记录:(PS.点击版本号下载对应的版本) +## 更新记录: + [v1.0.0] 优化后第一版 + [v1.1.0] 新增在线升级、缓存清理、加入QQ群功能 diff --git a/app/build.gradle b/app/build.gradle index a880cc3e..4f2b40e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -116,7 +116,7 @@ void cmdExecute(String cmd) { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' @@ -147,6 +147,8 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.20' //RxJava - implementation "io.reactivex.rxjava3:rxjava:3.1.1" + implementation 'io.reactivex.rxjava3:rxjava:3.1.1' + //AndroidAsync + implementation 'com.koushikdutta.async:androidasync:3.1.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c2d6d0ed..d9cb87be 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ package="com.idormy.sms.forwarder"> + + diff --git a/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java b/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java new file mode 100644 index 00000000..49600d3b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java @@ -0,0 +1,238 @@ +package com.idormy.sms.forwarder; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import com.idormy.sms.forwarder.receiver.RebootBroadcastReceiver; +import com.idormy.sms.forwarder.utils.LogUtil; +import com.idormy.sms.forwarder.utils.NetUtil; +import com.idormy.sms.forwarder.view.IPEditText; +import com.koushikdutta.async.http.WebSocket; +import com.koushikdutta.async.http.server.AsyncHttpServer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class CloneActivity extends AppCompatActivity { + private final String TAG = "com.idormy.sms.forwarder.CloneActivity"; + private Context context; + private boolean isRunning = false; + private String serverIp; + private final String DATABASE_NAME = "sms_forwarder.db"; + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + + context = CloneActivity.this; + + setContentView(R.layout.activity_clone); + Log.d(TAG, "onCreate: " + RebootBroadcastReceiver.class.getName()); + + } + + @SuppressLint("SetTextI18n") + @Override + protected void onStart() { + super.onStart(); + Log.d(TAG, "onStart"); + + IPEditText textServerIp = findViewById(R.id.textServerIp); + + List _sockets = new ArrayList<>(); + AsyncHttpServer server = new AsyncHttpServer(); + + TextView sendTxt = findViewById(R.id.sendTxt); + TextView receiveTxt = findViewById(R.id.receiveTxt); + + Button sendBtn = findViewById(R.id.sendBtn); + sendBtn.setOnClickListener(v -> { + if (NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) { + Toast.makeText(CloneActivity.this, R.string.no_wifi_network, Toast.LENGTH_SHORT).show(); + return; + } else { + serverIp = NetUtil.getLocalIp(CloneActivity.this); + TextView ipText = findViewById(R.id.ipText); + ipText.setText(getString(R.string.local_ip) + serverIp); + } + if (!isRunning) { + isRunning = true; + server.get("/", (request, response) -> { + File file = context.getDatabasePath(DATABASE_NAME); + response.getHeaders().add("Content-Disposition", "attachment;filename=" + DATABASE_NAME); + response.sendFile(file); + }); + server.listen(5000); + Toast.makeText(CloneActivity.this, R.string.server_has_started, Toast.LENGTH_SHORT).show(); + sendTxt.setText(R.string.server_has_started); + textServerIp.setIP(serverIp); + sendBtn.setText(R.string.stop); + } else { + isRunning = false; + server.stop(); + Toast.makeText(CloneActivity.this, R.string.server_has_stopped, Toast.LENGTH_SHORT).show(); + sendTxt.setText(R.string.server_has_stopped); + textServerIp.setIP(""); + sendBtn.setText(R.string.send); + } + }); + + Button receiveBtn = findViewById(R.id.receiveBtn); + receiveBtn.setOnClickListener(v -> { + if (isRunning) { + receiveTxt.setText(R.string.sender_cannot_receive); + Toast.makeText(CloneActivity.this, R.string.sender_cannot_receive, Toast.LENGTH_SHORT).show(); + return; + } + + if (NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) { + receiveTxt.setText(R.string.no_wifi_network); + Toast.makeText(CloneActivity.this, R.string.no_wifi_network, Toast.LENGTH_SHORT).show(); + return; + } + + serverIp = textServerIp.getIP(); + if (serverIp == null || serverIp.isEmpty()) { + receiveTxt.setText(R.string.invalid_server_ip); + Toast.makeText(CloneActivity.this, R.string.invalid_server_ip, Toast.LENGTH_SHORT).show(); + return; + } + + //下载连接 + final String url = "http://" + serverIp + ":5000/"; + Log.d(TAG, url); + //保存路径 + final String savePath = context.getCacheDir().getPath() + File.separator + DATABASE_NAME; + Log.d(TAG, savePath); + final long startTime = System.currentTimeMillis(); + Log.i(TAG, "startTime=" + startTime); + OkHttpClient okHttpClient = new OkHttpClient(); + Request request = new Request.Builder().url(url).addHeader("Connection", "close").build(); + okHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + e.printStackTrace(); + //Toast.makeText(CloneActivity.this, R.string.download_failed + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + InputStream is = null; + byte[] buf = new byte[2048]; + int len; + FileOutputStream fos = null; + + try { + is = Objects.requireNonNull(response.body()).byteStream(); + long total = Objects.requireNonNull(response.body()).contentLength(); + File file = new File(savePath, url.substring(url.lastIndexOf("/") + 1)); + fos = new FileOutputStream(file); + long sum = 0; + while ((len = is.read(buf)) != -1) { + fos.write(buf, 0, len); + sum += len; + int progress = (int) (sum * 1.0f / total * 100); + Log.e(TAG, "download progress : " + progress); + } + fos.flush(); + Log.e(TAG, "download success"); + Log.e(TAG, "totalTime=" + (System.currentTimeMillis() - startTime)); + //Toast.makeText(CloneActivity.this, R.string.download_success, Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + e.printStackTrace(); + //Toast.makeText(CloneActivity.this, R.string.download_failed + e.getMessage(), Toast.LENGTH_SHORT).show(); + } finally { + try { + if (is != null) is.close(); + } catch (IOException ignored) { + } + try { + if (fos != null) fos.close(); + } catch (IOException ignored) { + } + } + } + }); + + //TODO:替换sqlite + File dbFile = new File(savePath); + FileInputStream fis; + try { + fis = new FileInputStream(dbFile); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return; + } + + String outFileName = context.getDatabasePath(DATABASE_NAME).getAbsolutePath(); + Log.d(TAG, outFileName); + + // Open the empty db as the output stream + OutputStream output; + try { + output = new FileOutputStream(outFileName); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return; + } + + // Transfer bytes from the input file to the output file + byte[] buffer = new byte[1024]; + int length; + while (true) { + try { + if (!((length = fis.read(buffer)) > 0)) break; + output.write(buffer, 0, length); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Close the streams + try { + output.flush(); + output.close(); + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + LogUtil.delLog(null, null); + + receiveTxt.setText(R.string.download_success); + }); + + } + + @SuppressLint("SetTextI18n") + @Override + protected void onResume() { + super.onResume(); + + serverIp = NetUtil.getLocalIp(CloneActivity.this); + TextView ipText = findViewById(R.id.ipText); + ipText.setText(getString(R.string.local_ip) + serverIp); + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java b/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java index abd4c590..8be2ef79 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java +++ b/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java @@ -171,6 +171,11 @@ public class MainActivity extends AppCompatActivity implements RefreshListView.I builder.show(); } + public void toClone() { + Intent intent = new Intent(this, CloneActivity.class); + startActivity(intent); + } + public void toSetting() { Intent intent = new Intent(this, SettingActivity.class); startActivity(intent); @@ -217,6 +222,9 @@ public class MainActivity extends AppCompatActivity implements RefreshListView.I public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { + case R.id.to_clone: + toClone(); + return true; case R.id.to_setting: toSetting(); return true; diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtil.java b/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtil.java index ccce39d8..087bd49b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtil.java +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtil.java @@ -4,15 +4,19 @@ import android.annotation.SuppressLint; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.widget.Toast; +import com.idormy.sms.forwarder.R; + public class NetUtil { //没有网络 - private static final int NETWORK_NONE = 0; + public static final int NETWORK_NONE = 0; //移动网络 - private static final int NETWORK_MOBILE = 1; + public static final int NETWORK_MOBILE = 1; //无线网络 - private static final int NETWORK_WIFI = 2; + public static final int NETWORK_WIFI = 2; static Boolean hasInit = false; @SuppressLint("StaticFieldLeak") @@ -39,20 +43,31 @@ public class NetUtil { //判断是否是wifi if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) { //返回无线网络 - Toast.makeText(context, "当前处于无线网络", Toast.LENGTH_SHORT).show(); + Toast.makeText(context, R.string.on_wireless_network, Toast.LENGTH_SHORT).show(); return NETWORK_WIFI; //判断是否移动网络 } else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) { - Toast.makeText(context, "当前处于移动网络", Toast.LENGTH_SHORT).show(); + Toast.makeText(context, R.string.on_mobile_network, Toast.LENGTH_SHORT).show(); //返回移动网络 return NETWORK_MOBILE; } } else { //没有网络 - Toast.makeText(context, "当前没有网络", Toast.LENGTH_SHORT).show(); + Toast.makeText(context, R.string.no_network, Toast.LENGTH_SHORT).show(); return NETWORK_NONE; } //默认返回 没有网络 return NETWORK_NONE; } + + public static String getLocalIp(Context context) { + if (NETWORK_WIFI != getNetWorkStatus()) return context.getString(R.string.not_connected_wifi); + + WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + int ipAddress = wifiInfo.getIpAddress(); + if (ipAddress == 0) return context.getString(R.string.failed_to_get_ip); + return ((ipAddress & 0xff) + "." + (ipAddress >> 8 & 0xff) + "." + + (ipAddress >> 16 & 0xff) + "." + (ipAddress >> 24 & 0xff)); + } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/view/IPEditText.java b/app/src/main/java/com/idormy/sms/forwarder/view/IPEditText.java new file mode 100644 index 00000000..33b1504b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/view/IPEditText.java @@ -0,0 +1,255 @@ +package com.idormy.sms.forwarder.view; + +import android.content.Context; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.idormy.sms.forwarder.R; + +import java.util.regex.Pattern; + +public class IPEditText extends LinearLayout { + + //控件 + private final EditText Edit1; + private final EditText Edit2; + private final EditText Edit3; + private final EditText Edit4; + private String ip1; + private String ip2; + private String ip3; + private String ip4; + + public IPEditText(final Context context, AttributeSet attrs) { + super(context, attrs); + //初始化界面 + View view = LayoutInflater.from(context).inflate(R.layout.iptext, this); + //绑定 + Edit1 = findViewById(R.id.edit1); + Edit2 = findViewById(R.id.edit2); + Edit3 = findViewById(R.id.edit3); + Edit4 = findViewById(R.id.edit4); + //初始化函数 + init(context); + } + + private void init(final Context context) { + /* + 监听文本,得到ip段,自动进入下一个输入框 + */ + Edit1.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + ip1 = s.toString().trim(); + int lenIp1 = ip1.length(); + if (lenIp1 > 0 && !Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.?$", ip1)) { + ip1 = ip1.substring(0, lenIp1 - 1); + Edit1.setText(ip1); + Edit1.setSelection(ip1.length()); + Toast.makeText(context, R.string.invalid_ip, Toast.LENGTH_LONG).show(); + return; + } + //非空输入 . 跳到下一个输入框 + if (lenIp1 > 1 && ".".equals(ip1.substring(lenIp1 - 1))) { + ip1 = ip1.substring(0, lenIp1 - 1); + Edit1.setText(ip1); + Edit2.setFocusable(true); + Edit2.requestFocus(); + return; + } + //已输3位数字,跳到下一个输入框 + if (lenIp1 > 2) { + Edit2.setFocusable(true); + Edit2.requestFocus(); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + + Edit2.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + ip2 = s.toString().trim(); + int lenIp2 = ip2.length(); + if (lenIp2 > 0 && !Pattern.matches("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.?$", ip2)) { + ip2 = ip2.substring(0, lenIp2 - 1); + Edit2.setText(ip2); + Edit2.setSelection(ip2.length()); + Toast.makeText(context, R.string.invalid_ip, Toast.LENGTH_LONG).show(); + return; + } + //非空输入 . 跳到下一个输入框 + if (lenIp2 > 1 && ".".equals(ip2.substring(lenIp2 - 1))) { + ip2 = ip2.substring(0, lenIp2 - 1); + Edit2.setText(ip2); + Edit3.setFocusable(true); + Edit3.requestFocus(); + return; + } + //已输3位数字,跳到下一个输入框 + if (lenIp2 > 2) { + Edit3.setFocusable(true); + Edit3.requestFocus(); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + Edit3.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + ip3 = s.toString().trim(); + int lenIp3 = ip3.length(); + if (lenIp3 > 0 && !Pattern.matches("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.?$", ip3)) { + ip3 = ip3.substring(0, lenIp3 - 1); + Edit3.setText(ip3); + Edit3.setSelection(ip3.length()); + Toast.makeText(context, R.string.invalid_ip, Toast.LENGTH_LONG).show(); + return; + } + //非空输入 . 跳到下一个输入框 + if (lenIp3 > 1 && ".".equals(ip3.substring(lenIp3 - 1))) { + ip3 = ip3.substring(0, lenIp3 - 1); + Edit3.setText(ip3); + Edit4.setFocusable(true); + Edit4.requestFocus(); + return; + } + //已输3位数字,跳到下一个输入框 + if (lenIp3 > 2) { + Edit4.setFocusable(true); + Edit4.requestFocus(); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + Edit4.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + ip4 = s.toString().trim(); + int lenIp4 = ip4.length(); + if (lenIp4 > 0 && !Pattern.matches("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$", ip4)) { + ip4 = ip4.substring(0, lenIp4 - 1); + Edit4.setText(ip4); + Edit4.setSelection(ip4.length()); + Toast.makeText(context, R.string.invalid_ip, Toast.LENGTH_LONG).show(); + return; + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + /* + 监听控件,空值时del键返回上一输入框 + */ + Edit2.setOnKeyListener((v, keyCode, event) -> { + if (ip2 == null || ip2.isEmpty()) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + Edit1.setFocusable(true); + Edit1.requestFocus(); + Edit1.setSelection(ip1.length()); + } + } + return false; + }); + Edit3.setOnKeyListener((v, keyCode, event) -> { + if (ip3 == null || ip3.isEmpty()) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + Edit2.setFocusable(true); + Edit2.requestFocus(); + Edit2.setSelection(ip2.length()); + } + } + return false; + }); + Edit4.setOnKeyListener((v, keyCode, event) -> { + if (ip4 == null || ip4.isEmpty()) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + Edit3.setFocusable(true); + Edit3.requestFocus(); + Edit3.setSelection(ip3.length()); + } + } + return false; + }); + } + + /** + * 成员函数,返回整个ip地址 + */ + public String getIP() { + //文本 + String text; + if (TextUtils.isEmpty(ip1) || TextUtils.isEmpty(ip2) + || TextUtils.isEmpty(ip3) || TextUtils.isEmpty(ip4)) { + text = null; + } else { + text = ip1 + "." + ip2 + "." + ip3 + "." + ip4; + } + return text; + } + + /** + * 成员函数,返回整个ip地址 + */ + public void setIP(String ip) { + if (ip == null || ip.isEmpty() + || !Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ip)) { + ip1 = ""; + ip2 = ""; + ip3 = ""; + ip4 = ""; + } else { + String[] ips = ip.split("\\."); + ip1 = ips[0]; + ip2 = ips[1]; + ip3 = ips[2]; + ip4 = ips[3]; + } + + Edit1.setText(ip1); + Edit2.setText(ip2); + Edit3.setText(ip3); + Edit4.setText(ip4); + } +} diff --git a/app/src/main/res/drawable-mdpi/receive_btn.xml b/app/src/main/res/drawable-mdpi/receive_btn.xml new file mode 100644 index 00000000..38ce05a9 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/receive_btn.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-mdpi/send_btn.xml b/app/src/main/res/drawable-mdpi/send_btn.xml new file mode 100644 index 00000000..9956eb1b --- /dev/null +++ b/app/src/main/res/drawable-mdpi/send_btn.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_clone.xml b/app/src/main/res/layout/activity_clone.xml new file mode 100644 index 00000000..792820aa --- /dev/null +++ b/app/src/main/res/layout/activity_clone.xml @@ -0,0 +1,119 @@ + + + + + + + + + +