Skip to content
Open
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
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/build
src/main/assets/ariang
25 changes: 24 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import com.github.triplet.gradle.androidpublisher.ReleaseStatus

plugins {
id 'com.github.triplet.play' version '3.12.1'
id 'de.undercouch.download' version '5.6.0'
}

apply plugin: 'com.android.application'
Expand All @@ -21,6 +22,9 @@ android {
versionCode 77
versionName "2.7.0"
}
buildFeatures {
viewBinding true
}

if (isCi) {
signingConfigs {
Expand Down Expand Up @@ -88,6 +92,7 @@ android {
useLegacyPackaging = true
}
}
compileSdk 36
}

play {
Expand All @@ -108,4 +113,22 @@ dependencies {
}
}

apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.gms.google-services'

tasks.register('downloadAriaNg', Download) {
src 'https://github.com/mayswind/AriaNg/releases/download/1.3.11/AriaNg-1.3.11-AllInOne.zip'
dest layout.buildDirectory.file('AriaNg-1.3.11-AllInOne.zip')
overwrite false
}

tasks.register('installAriaNg', Copy) {
dependsOn downloadAriaNg
from zipTree(tasks.downloadAriaNg.dest)
into project.file('src/main/assets/ariang')
}
tasks.named("preBuild").configure {
dependsOn tasks.installAriaNg
}
tasks.named('clean').configure {
delete project.file('src/main/assets/ariang')
}
9 changes: 8 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<!-- You know... -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Import bin -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Start service on boot -->
Expand All @@ -30,6 +31,7 @@
android:label="@string/app_name"
android:resizeableActivity="true"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">

Expand Down Expand Up @@ -64,11 +66,16 @@
</intent-filter>
</activity>

<activity android:name=".AriaNgActivity"
android:theme="@style/AppTheme.NoActionBar"/>
<!-- Preferences activity -->
<activity
android:name=".PreferenceActivity"
android:configChanges="uiMode" />

<activity
android:name=".ConfigEditorActivity"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"
android:configChanges="uiMode" />
<!-- Boot completed receiver -->
<receiver
android:name=".BootCompletedReceiver"
Expand Down
70 changes: 70 additions & 0 deletions app/src/main/java/com/gianlu/aria2android/AriaNgActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.gianlu.aria2android;

import android.os.Bundle;
import android.util.Base64;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;

import androidx.annotation.Nullable;

import com.gianlu.aria2android.databinding.AriangLayoutBinding;
import com.gianlu.aria2lib.Aria2PK;

import java.io.IOException;
import java.util.Objects;

public class AriaNgActivity extends BastActivity {
WebView webView;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AriangLayoutBinding binding = AriangLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
webView = binding.ariangWebview;
setupWebView();
}

void setupWebView() {
WebSettings settings = webView.getSettings();
webView.setWebViewClient(new WebViewClient());
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
webView.loadUrl(getUri());
}

private String getUri() {
String secret = Base64.encodeToString(Aria2PK.RPC_TOKEN.fallback().getBytes(), Base64.DEFAULT);
return "http://ariang.local/index.html" +
"#!/settings/rpc/set?" +
"protocol=ws&" +
"host=127.0.0.1&" +
"port=" + Aria2PK.RPC_PORT.fallback() + "&" +
"interface=jsonrpc&" +
"secret=" + secret;
}

@Override
protected void onDestroy() {
super.onDestroy();
if (webView != null) webView.destroy();
}

static class WebViewClient extends android.webkit.WebViewClient {
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (Objects.equals(request.getUrl().getPath(), "/index.html")) {
try {
return new WebResourceResponse("text/html", "utf-8",
view.getContext().getAssets().open("ariang/index.html"));
} catch (IOException e) {
return null;
}
}
return super.shouldInterceptRequest(view, request);
}
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/gianlu/aria2android/BastActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gianlu.aria2android;

import com.gianlu.commonutils.dialogs.ActivityWithDialog;

public class BastActivity extends ActivityWithDialog {
}
205 changes: 205 additions & 0 deletions app/src/main/java/com/gianlu/aria2android/ConfigEditorActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package com.gianlu.aria2android;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;

import com.gianlu.aria2android.databinding.ConfigEditorBinding;
import com.gianlu.aria2lib.Aria2PK;
import com.gianlu.aria2lib.R;
import com.gianlu.aria2lib.ui.ImportExportUtils;
import com.gianlu.aria2lib.ui.SimpleOptionsAdapter;
import com.gianlu.commonutils.CommonUtils;
import com.gianlu.commonutils.misc.RecyclerMessageView;
import com.gianlu.commonutils.preferences.CommonPK;
import com.gianlu.commonutils.preferences.Prefs;
import com.gianlu.commonutils.preferences.json.JsonStoring;
import com.gianlu.commonutils.ui.Toaster;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;

import org.json.JSONException;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ConfigEditorActivity extends BastActivity implements SimpleOptionsAdapter.Listener {
private static final int IMPORT_CODE = 1;
private static final String TAG = "ConfigEditorActivity";
private SimpleOptionsAdapter adapter;
private RecyclerMessageView rmv;

private void load() throws JSONException {
adapter.load(JsonStoring.intoPrefs().getJsonObject(Aria2PK.CUSTOM_OPTIONS));
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ConfigEditorBinding binding = ConfigEditorBinding.inflate(getLayoutInflater());
rmv = binding.content;
setContentView(binding.getRoot());
binding.toolbar.setTitle(R.string.customOptions);
binding.toolbar.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
binding.toolbar.setNavigationOnClickListener(view -> onBackPressed());

getMenuInflater().inflate(R.menu.aria2lib_config_editor, binding.toolbar.getMenu());
binding.toolbar.setOnMenuItemClickListener(this::onOptionsItemSelected);
setNight();

rmv.linearLayoutManager(RecyclerView.VERTICAL, false);
rmv.dividerDecoration(RecyclerView.VERTICAL);
adapter = new SimpleOptionsAdapter(this, this);
rmv.loadListData(adapter);

try {
load();
} catch (JSONException ex) {
Log.e(TAG, "Failed loading JSON.", ex);
Toaster.with(this).message(R.string.failedLoadingOptions).show();
onBackPressed();
}
}

private void setNight() {
boolean b = Prefs.getBoolean(CommonPK.NIGHT_MODE, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decor = getWindow().getDecorView();
int flags = decor.getSystemUiVisibility();
if (!b) {
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
decor.setSystemUiVisibility(flags);
}
}

private void save() {
try {
JsonStoring.intoPrefs().putJsonObject(Aria2PK.CUSTOM_OPTIONS, ImportExportUtils.toJson(adapter.get()));
adapter.saved();
} catch (JSONException ex) {
Log.e(TAG, "Failed saving JSON.", ex);
Toaster.with(this).message(R.string.failedSavingCustomOptions).show();
}
}

@SuppressLint("InflateParams")
private void showAddDialog() {
LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.aria2lib_dialog_new_option, null, false);
TextInputLayout key = layout.findViewById(R.id.editOptionDialog_key);
TextInputLayout value = layout.findViewById(R.id.editOptionDialog_value);

MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.newOption).setView(layout)
.setPositiveButton(R.string.apply, (dialogInterface, i) -> {
String keyStr = CommonUtils.getText(key);
if (keyStr.startsWith("--")) keyStr = keyStr.substring(2);
adapter.add(new Pair<>(keyStr, CommonUtils.getText(value)));
}).setNegativeButton(android.R.string.cancel, null);

showDialog(builder);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == IMPORT_CODE) {
if (resultCode == Activity.RESULT_OK && data.getData() != null) {
try {
InputStream in = getContentResolver().openInputStream(data.getData());
if (in == null || adapter == null) return;

try {
adapter.add(ImportExportUtils.readConfigFromStream(in));
} catch (IOException | OutOfMemoryError ex) {
Toaster.with(this).message(R.string.cannotImport).show();
}
} catch (FileNotFoundException ex) {
Toaster.with(this).message(R.string.fileNotFound).show();
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
if (id == R.id.configEditor_add) {
showAddDialog();
return true;
} else if (id == android.R.id.home) {
if (adapter.hasChanged()) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.unsavedChanges)
.setMessage(R.string.unsavedChanges_message)
.setPositiveButton(R.string.yes, (dialogInterface, i) -> {
save();
onBackPressed();
})
.setNegativeButton(R.string.no, (dialogInterface, i) -> onBackPressed())
.setNeutralButton(android.R.string.cancel, null);

showDialog(builder);
} else {
onBackPressed();
}

return true;
} else if (id == R.id.configEditor_import) {
try {
startActivityForResult(Intent.createChooser(ImportExportUtils.createConfigImportIntent(), getString(R.string.importConfig)), IMPORT_CODE);
} catch (ActivityNotFoundException ex) {
Toaster.with(this).message(R.string.missingFileExplorer).show();
}
return true;
} else if (id == R.id.configEditor_done) {
save();
onBackPressed();
return true;
}

return super.onOptionsItemSelected(item);
}

@Override
@SuppressLint("InflateParams")
public void onEditOption(@NonNull Pair<String, String> option) {
FrameLayout layout = (FrameLayout) getLayoutInflater().inflate(R.layout.aria2lib_dialog_edit_option, null, false);
TextInputLayout newValue = layout.findViewById(R.id.editOptionDialog_value);
CommonUtils.setText(newValue, option.second);

MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(option.first)
.setView(layout)
.setPositiveButton(R.string.apply, (dialogInterface, i) -> {
String newValueStr = CommonUtils.getText(newValue);
if (!newValueStr.equals(option.second))
adapter.set(new Pair<>(option.first, newValueStr));
})
.setNegativeButton(android.R.string.cancel, null);

showDialog(builder);
}

@Override
public void onItemsCountChanged(int count) {
if (count <= 0) rmv.showInfo(R.string.noCustomOptions);
else rmv.showList();
}
}
Loading