Compare commits
67 Commits
android-cl
...
android-0.
Author | SHA1 | Date | |
---|---|---|---|
9654fa24cc | |||
7843b37a7e | |||
1db9128afc | |||
c03d3a8b92 | |||
80b7455602 | |||
1fcf5aa49b | |||
640803418d | |||
56fa0b0302 | |||
9c10eef0e3 | |||
9fd5e43115 | |||
d5bd9b8eaa | |||
5b1203a1c6 | |||
fbe79eee2e | |||
ffa21fc1e0 | |||
5faf1f5bb0 | |||
e15efb6537 | |||
cd1702d53c | |||
5a6ca8a0a4 | |||
82d184cf90 | |||
5162bb604b | |||
3aff2a7a9d | |||
cae565761d | |||
c2b6cee9a2 | |||
84e7b1f41c | |||
7ebed1e6d2 | |||
2e68122b8d | |||
899a3f4cfc | |||
52d49a4ab6 | |||
75f705125f | |||
722ddf8a47 | |||
524d21631e | |||
86e6060217 | |||
b5c7fad876 | |||
5f3ca0fe69 | |||
ddd9bea786 | |||
6aac99e7ea | |||
da763a7c81 | |||
6cb46c3168 | |||
610e963d22 | |||
96b8ed43e0 | |||
812c28cd33 | |||
3f7312653a | |||
c89d3992c7 | |||
d9394685c9 | |||
b140158b24 | |||
4f5b0bd21a | |||
1d17d89ccb | |||
b6074da7c4 | |||
c0fdb4aff7 | |||
e00c9cc449 | |||
423ca46672 | |||
66ed9d94a7 | |||
a68ef9d372 | |||
cb389123c5 | |||
62cca0ed50 | |||
c99e3c0b41 | |||
302c51ccfa | |||
1e34bc2159 | |||
24f6f4789d | |||
2de11a4067 | |||
d83a2f9919 | |||
bf36b4c2e6 | |||
daa0b739a3 | |||
5e1b0d9b50 | |||
917742847a | |||
9460e3202f | |||
5f388a7c6b |
@ -4,15 +4,15 @@ lang_map = he: iw, id: in, pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA
|
|||||||
|
|
||||||
[I2P.android]
|
[I2P.android]
|
||||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||||
|
minimum_perc = 50
|
||||||
source_file = app/src/main/res/values/strings.xml
|
source_file = app/src/main/res/values/strings.xml
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = ANDROID
|
type = ANDROID
|
||||||
minimum_perc = 50
|
|
||||||
|
|
||||||
[I2P.android_lib_helper]
|
[I2P.android_lib_helper]
|
||||||
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
|
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
|
||||||
|
minimum_perc = 50
|
||||||
source_file = lib/helper/src/main/res/values/strings.xml
|
source_file = lib/helper/src/main/res/values/strings.xml
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = ANDROID
|
type = ANDROID
|
||||||
minimum_perc = 50
|
|
||||||
|
|
||||||
|
39
CHANGELOG
@ -1,3 +1,42 @@
|
|||||||
|
0.9.33 / 2018-02-18
|
||||||
|
* Translation updates
|
||||||
|
|
||||||
|
0.9.32 / 2017-11-28
|
||||||
|
* Fixed "Application Not Responding" error when restarting all tunnels
|
||||||
|
* Fixed crashes when:
|
||||||
|
* opening the console menu
|
||||||
|
* starting the router
|
||||||
|
* starting the router for the first time
|
||||||
|
* viewing tunnels
|
||||||
|
* viewing the addressbook
|
||||||
|
* opening the addressbook menu
|
||||||
|
* configuring addressbook subscriptions
|
||||||
|
* using a configuration wizard
|
||||||
|
* loading a B64 Destination from file
|
||||||
|
* viewing tunnel settings
|
||||||
|
* saving tunnel settings
|
||||||
|
* installing a tunnel's recommended app without a market app
|
||||||
|
* installing a browser without a market app
|
||||||
|
* rotating the screen while using the built-in browser
|
||||||
|
* Added a "sync" icon to more clearly indicate tunnel "starting" status
|
||||||
|
* Updated Firefox browser config instructions for Firefox Quantum
|
||||||
|
* Translation updates
|
||||||
|
|
||||||
|
0.9.31 / 2017-08-19
|
||||||
|
* Fixed various crashes in the Tunnels UI
|
||||||
|
* Updated Firefox browser config instructions
|
||||||
|
* Minor bug fixes
|
||||||
|
* Dependency and translation updates
|
||||||
|
|
||||||
|
0.9.30 / 2017-05-20
|
||||||
|
* Fixed crashes when creating or deleting tunnels, or adding names to the
|
||||||
|
private addressbook
|
||||||
|
* Minor bug fixes
|
||||||
|
* Dependency and translation updates
|
||||||
|
|
||||||
|
0.9.29 / 2017-03-27
|
||||||
|
* Dependency and translation updates
|
||||||
|
|
||||||
0.9.28 / 2017-01-02
|
0.9.28 / 2017-01-02
|
||||||
* Bug fixes and translation updates
|
* Bug fixes and translation updates
|
||||||
|
|
||||||
|
20
RELEASE-PROCESS.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
1. Check out a clean copy of i2p.i2p at the correct release version.
|
||||||
|
2. Edit `routerjars/local.properties` to use the clean i2p.i2p copy.
|
||||||
|
3. Pull the latest translations with `tx pull -a` and commit them.
|
||||||
|
4. Ensure that `signing.properties` contains the details of the release key.
|
||||||
|
5. Edit `gradle.properties` to bump the I2P version.
|
||||||
|
6. Edit `app/build.gradle` to bump the Android version number.
|
||||||
|
7. If the helper has changed since the last release, edit
|
||||||
|
`lib/helper/gradle.properties` to bump the version.
|
||||||
|
8. `./gradlew clean assembleRelease`
|
||||||
|
9. `./gradlew :lib:client:uploadArchives`
|
||||||
|
10. If the helper version was changed: `./gradlew :lib:helper:uploadArchives`
|
||||||
|
11. Check on Sonatype that everything worked, and close/release.
|
||||||
|
12. Update local fdroidserver repo
|
||||||
|
13. `cp app/build/outputs/apk/free/release/app-free-release.apk path/to/fdroid/repo/I2P-VERSION.apk`
|
||||||
|
14. Update `path/to/fdroid/metadata/net.i2p.android.txt`
|
||||||
|
15. `fdroid update`
|
||||||
|
16. Push to download server and put in place.
|
||||||
|
17. Check F-Droid repo works, and app works.
|
||||||
|
18. `mtn ci gradle.properties lib/helper/gradle.properties app/build.gradle`
|
||||||
|
19. Push free and donate builds to Google Play.
|
@ -1,11 +1,9 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'witness'
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION as String)
|
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION as String)
|
||||||
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION as String
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode 4745235
|
versionCode 4745241
|
||||||
versionName "$I2P_VERSION"
|
versionName "$I2P_VERSION"
|
||||||
minSdkVersion 9
|
minSdkVersion 9
|
||||||
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
|
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
|
||||||
@ -23,6 +21,7 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
|
debuggable true
|
||||||
applicationIdSuffix '.debug'
|
applicationIdSuffix '.debug'
|
||||||
versionNameSuffix '-DEBUG'
|
versionNameSuffix '-DEBUG'
|
||||||
}
|
}
|
||||||
@ -37,14 +36,18 @@ android {
|
|||||||
packagingOptions {
|
packagingOptions {
|
||||||
exclude 'LICENSE.txt'
|
exclude 'LICENSE.txt'
|
||||||
}
|
}
|
||||||
|
flavorDimensions 'tier'
|
||||||
productFlavors {
|
productFlavors {
|
||||||
free {
|
free {
|
||||||
|
dimension 'tier'
|
||||||
applicationId 'net.i2p.android'
|
applicationId 'net.i2p.android'
|
||||||
}
|
}
|
||||||
donate {
|
donate {
|
||||||
|
dimension 'tier'
|
||||||
applicationId 'net.i2p.android.donate'
|
applicationId 'net.i2p.android.donate'
|
||||||
}
|
}
|
||||||
legacy {
|
legacy {
|
||||||
|
dimension 'tier'
|
||||||
applicationId 'net.i2p.android.router'
|
applicationId 'net.i2p.android.router'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,49 +55,34 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Local dependencies
|
// Local dependencies
|
||||||
compile project(':lib:client')
|
implementation project(':lib:client')
|
||||||
compile project(':lib:helper')
|
implementation project(':lib:helper')
|
||||||
compile project(':routerjars')
|
implementation project(path: ':routerjars', configuration: 'routerjars')
|
||||||
|
|
||||||
// Android Support Repository dependencies
|
// Android Support Repository dependencies
|
||||||
def supportVersion = '25.3.0'
|
def supportVersion = '25.3.1'
|
||||||
compile "com.android.support:support-v4:$supportVersion"
|
implementation "com.android.support:support-v4:$supportVersion"
|
||||||
compile "com.android.support:appcompat-v7:$supportVersion"
|
implementation "com.android.support:appcompat-v7:$supportVersion"
|
||||||
compile "com.android.support:preference-v7:$supportVersion"
|
implementation "com.android.support:preference-v7:$supportVersion"
|
||||||
compile "com.android.support:preference-v14:$supportVersion"
|
implementation "com.android.support:preference-v14:$supportVersion"
|
||||||
compile "com.android.support:recyclerview-v7:$supportVersion"
|
implementation "com.android.support:recyclerview-v7:$supportVersion"
|
||||||
|
|
||||||
// Remote dependencies
|
// Remote dependencies
|
||||||
compile 'com.androidplot:androidplot-core:1.4.1'
|
implementation 'com.androidplot:androidplot-core:1.4.1'
|
||||||
compile 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
|
implementation 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
|
||||||
compile 'com.inkapplications.viewpageindicator:library:2.4.4'
|
implementation 'com.inkapplications.viewpageindicator:library:2.4.4'
|
||||||
compile 'com.pnikosis:materialish-progress:1.7'
|
implementation 'com.pnikosis:materialish-progress:1.7'
|
||||||
compile "net.i2p:router:$I2P_VERSION"
|
implementation "net.i2p:router:$I2P_VERSION"
|
||||||
compile 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
||||||
compile 'org.sufficientlysecure:html-textview:3.1'
|
implementation 'org.sufficientlysecure:html-textview:3.1'
|
||||||
|
|
||||||
// Testing-only dependencies
|
// Testing-only dependencies
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||||
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
project.ext.i2pbase = "../i2p.i2p"
|
||||||
verify = [
|
|
||||||
'com.android.support:support-v4:9142d2f386eeb4483050d7f13fcaeddcd01011650f4216208376ff7d4d7a6bf1',
|
|
||||||
'com.android.support:appcompat-v7:2841b0786c50018a14b6bbcdadb63d6b077ea9e2aadcc7611de1caeecaccf06a',
|
|
||||||
'com.android.support:preference-v7:0eede78cace2404e107886a4eaf11c4b895df949a4f1deee58e2e60e25c73dd2',
|
|
||||||
'com.android.support:preference-v14:19d384a0476cf6e03fc857a29c3d7f2055c701f828b502c78e80b66b2bfe5ef4',
|
|
||||||
'com.android.support:recyclerview-v7:423b183809ef75051c150f5f401c4077ddedac26b2c515b6ee231c4c5724f5bf',
|
|
||||||
'com.androidplot:androidplot-core:7670da5838ce2ae2b0b5faabeb9d6a1f2787d30d3fe9f0952adc5611910d18c0',
|
|
||||||
'com.eowise:recyclerview-stickyheaders:7b236da49b33b840e9ba6e7e4182218d1a2d9047236fdbc3ca947352f9b0883b',
|
|
||||||
'com.inkapplications.viewpageindicator:library:77b26a7723cd10fa5e29480be239e8d68f431f4bc20d9144169c9ce06ebac2bf',
|
|
||||||
'com.pnikosis:materialish-progress:da089a90d1dab61e9b50038c09081019398f81190d12b0b567ce94b83ef8cf93',
|
|
||||||
'net.i2p:router:d5018bb262ab1e070efacb32811a72e149aaabe600dce32fb192d2ddadd285f3',
|
|
||||||
'net.i2p.android.ext:floatingactionbutton:09d43e2d4ac04a91bf7a37e1ec48a8d220204e3a55dca72cd36cd9fa27461ade',
|
|
||||||
'org.sufficientlysecure:html-textview:ed740adf05cae2373999c7a3047c803183d9807b2cf66162902090d7c112a832',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
project.ext.i2pbase = '../i2p.i2p'
|
|
||||||
def Properties props = new Properties()
|
def Properties props = new Properties()
|
||||||
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@ import net.i2p.router.RouterContext;
|
|||||||
import net.i2p.router.news.NewsEntry;
|
import net.i2p.router.news.NewsEntry;
|
||||||
import net.i2p.router.news.NewsMetadata;
|
import net.i2p.router.news.NewsMetadata;
|
||||||
import net.i2p.router.news.NewsXMLParser;
|
import net.i2p.router.news.NewsXMLParser;
|
||||||
import net.i2p.router.util.RFC822Date;
|
|
||||||
import net.i2p.util.EepGet;
|
import net.i2p.util.EepGet;
|
||||||
import net.i2p.util.FileUtil;
|
import net.i2p.util.FileUtil;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.ReusableGZIPInputStream;
|
import net.i2p.util.ReusableGZIPInputStream;
|
||||||
import net.i2p.util.SecureFileOutputStream;
|
import net.i2p.util.SecureFileOutputStream;
|
||||||
|
import net.i2p.util.RFC822Date;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.i2p.android.help;
|
package net.i2p.android.help;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.ColorMatrix;
|
import android.graphics.ColorMatrix;
|
||||||
@ -11,6 +12,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
@ -84,7 +86,11 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
|
|||||||
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
||||||
Uri uri = Uri.parse(uriMarket);
|
Uri uri = Uri.parse(uriMarket);
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
try {
|
||||||
mCtx.startActivity(intent);
|
mCtx.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(mCtx, R.string.no_market_app, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
holder.mStatus.setVisibility(View.VISIBLE);
|
holder.mStatus.setVisibility(View.VISIBLE);
|
||||||
|
@ -22,14 +22,17 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
import net.i2p.android.util.FragmentUtils;
|
import net.i2p.android.util.FragmentUtils;
|
||||||
import net.i2p.app.ClientAppState;
|
import net.i2p.app.ClientAppState;
|
||||||
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -41,6 +44,7 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
private TunnelControllerGroup mGroup;
|
private TunnelControllerGroup mGroup;
|
||||||
private TunnelEntry mTunnel;
|
private TunnelEntry mTunnel;
|
||||||
private Toolbar mToolbar;
|
private Toolbar mToolbar;
|
||||||
|
private ImageView mStatus;
|
||||||
|
|
||||||
public static TunnelDetailFragment newInstance(int tunnelId) {
|
public static TunnelDetailFragment newInstance(int tunnelId) {
|
||||||
TunnelDetailFragment f = new TunnelDetailFragment();
|
TunnelDetailFragment f = new TunnelDetailFragment();
|
||||||
@ -73,21 +77,33 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
String error;
|
String error;
|
||||||
|
List<TunnelController> controllers;
|
||||||
try {
|
try {
|
||||||
mGroup = TunnelControllerGroup.getInstance();
|
mGroup = TunnelControllerGroup.getInstance();
|
||||||
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||||
|
controllers = mGroup.getControllers();
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
mGroup = null;
|
mGroup = null;
|
||||||
|
controllers = null;
|
||||||
error = iae.toString();
|
error = iae.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mGroup == null) {
|
if (mGroup == null) {
|
||||||
// Show error
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
error, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
||||||
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
||||||
mTunnel = new TunnelEntry(getActivity(),
|
try {
|
||||||
mGroup.getControllers().get(tunnelId),
|
TunnelController controller = controllers.get(tunnelId);
|
||||||
tunnelId);
|
mTunnel = new TunnelEntry(getActivity(), controller, tunnelId);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
// Tunnel doesn't exist
|
||||||
|
Util.e("Could not load tunnel details", e);
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,18 +123,18 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
updateToolbar();
|
updateToolbar();
|
||||||
|
|
||||||
if (mTunnel != null) {
|
if (mTunnel != null) {
|
||||||
|
mStatus = (ImageView) v.findViewById(R.id.tunnel_status);
|
||||||
|
updateStatus();
|
||||||
|
ViewCompat.setTransitionName(mStatus, "status" + mTunnel.getId());
|
||||||
|
|
||||||
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||||
name.setText(mTunnel.getName());
|
name.setText(mTunnel.getName());
|
||||||
ViewCompat.setTransitionName(name,
|
|
||||||
getActivity().getString(R.string.TUNNEL_NAME) + mTunnel.getId());
|
|
||||||
|
|
||||||
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
|
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
|
||||||
type.setText(mTunnel.getType());
|
type.setText(mTunnel.getType());
|
||||||
|
|
||||||
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
||||||
description.setText(mTunnel.getDescription());
|
description.setText(mTunnel.getDescription());
|
||||||
ViewCompat.setTransitionName(description,
|
|
||||||
getActivity().getString(R.string.TUNNEL_DESCRIPTION) + mTunnel.getId());
|
|
||||||
|
|
||||||
if (!mTunnel.getDetails().isEmpty()) {
|
if (!mTunnel.getDetails().isEmpty()) {
|
||||||
v.findViewById(R.id.tunnel_details_container).setVisibility(View.VISIBLE);
|
v.findViewById(R.id.tunnel_details_container).setVisibility(View.VISIBLE);
|
||||||
@ -204,7 +220,13 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
Uri uri = mTunnel.getRecommendedAppForTunnel();
|
Uri uri = mTunnel.getRecommendedAppForTunnel();
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
try {
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.no_market_app,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -244,6 +266,14 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateStatus() {
|
||||||
|
mStatus.setImageDrawable(mTunnel.getStatusIcon());
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
mStatus.setBackgroundDrawable(mTunnel.getStatusBackground());
|
||||||
|
else
|
||||||
|
mStatus.setBackground(mTunnel.getStatusBackground());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean onToolbarItemSelected(MenuItem item) {
|
private boolean onToolbarItemSelected(MenuItem item) {
|
||||||
if (mTunnel == null)
|
if (mTunnel == null)
|
||||||
return false;
|
return false;
|
||||||
@ -257,6 +287,8 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||||
// Reload the toolbar to change the start/stop action
|
// Reload the toolbar to change the start/stop action
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
|
// Update the status icon
|
||||||
|
updateStatus();
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_stop_tunnel:
|
case R.id.action_stop_tunnel:
|
||||||
mTunnel.getController().stopTunnel();
|
mTunnel.getController().stopTunnel();
|
||||||
@ -265,42 +297,31 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||||
// Reload the toolbar to change the start/stop action
|
// Reload the toolbar to change the start/stop action
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
|
// Update the status icon
|
||||||
|
updateStatus();
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_edit_tunnel:
|
case R.id.action_edit_tunnel:
|
||||||
mCallback.onEditTunnel(mTunnel.getId());
|
mCallback.onEditTunnel(mTunnel.getId());
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_delete_tunnel:
|
case R.id.action_delete_tunnel:
|
||||||
DialogFragment dg = new DialogFragment() {
|
DialogFragment dg = DeleteTunnelDialogFragment.newInstance();
|
||||||
@NonNull
|
dg.show(getChildFragmentManager(), "delete_tunnel_dialog");
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setMessage(R.string.i2ptunnel_delete_confirm_message)
|
|
||||||
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
List<String> msgs = TunnelUtil.deleteTunnel(
|
|
||||||
I2PAppContext.getGlobalContext(),
|
|
||||||
mGroup, mTunnel.getId(), null);
|
|
||||||
dialog.dismiss();
|
|
||||||
Toast.makeText(getActivity().getApplicationContext(),
|
|
||||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
|
||||||
mCallback.onTunnelDeleted(mTunnel.getId(),
|
|
||||||
mGroup.getControllers().size());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
dg.show(getFragmentManager(), "delete_tunnel_dialog");
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onDeleteTunnel() {
|
||||||
|
List<String> msgs = TunnelUtil.deleteTunnel(
|
||||||
|
I2PAppContext.getGlobalContext(),
|
||||||
|
mGroup, mTunnel.getId(), null);
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||||
|
mCallback.onTunnelDeleted(mTunnel.getId(),
|
||||||
|
mGroup.getControllers().size());
|
||||||
|
}
|
||||||
|
|
||||||
private void copyToClipbardLegacy() {
|
private void copyToClipbardLegacy() {
|
||||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboard.setText(mTunnel.getDetails());
|
clipboard.setText(mTunnel.getDetails());
|
||||||
@ -313,4 +334,47 @@ public class TunnelDetailFragment extends Fragment {
|
|||||||
mTunnel.getName(), mTunnel.getDetails());
|
mTunnel.getName(), mTunnel.getDetails());
|
||||||
clipboard.setPrimaryClip(clip);
|
clipboard.setPrimaryClip(clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DeleteTunnelDialogFragment extends DialogFragment {
|
||||||
|
TunnelDetailFragment mListener;
|
||||||
|
|
||||||
|
public static DialogFragment newInstance() {
|
||||||
|
return new DeleteTunnelDialogFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAttachToParentFragment(Fragment fragment) {
|
||||||
|
// Verify that the host fragment implements the callback interface
|
||||||
|
try {
|
||||||
|
// Instantiate the TunnelDetailFragment so we can send events to the host
|
||||||
|
mListener = (TunnelDetailFragment) fragment;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// The fragment doesn't implement the interface, throw exception
|
||||||
|
throw new ClassCastException(fragment.toString()
|
||||||
|
+ " must be TunnelDetailFragment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
onAttachToParentFragment(getParentFragment());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
return new AlertDialog.Builder(getActivity())
|
||||||
|
.setMessage(R.string.i2ptunnel_delete_confirm_message)
|
||||||
|
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
mListener.onDeleteTunnel();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,19 @@ import android.net.Uri;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
|
||||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.data.PrivateKeyFile;
|
import net.i2p.data.PrivateKeyFile;
|
||||||
import net.i2p.i2ptunnel.TunnelController;
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class TunnelEntry {
|
public class TunnelEntry {
|
||||||
public static final int RUNNING = 1;
|
public static final int RUNNING = 1;
|
||||||
@ -26,18 +30,32 @@ public class TunnelEntry {
|
|||||||
private final TunnelController mController;
|
private final TunnelController mController;
|
||||||
private final int mId;
|
private final int mId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the new TunnelEntry, or null if there was an error.
|
||||||
|
*/
|
||||||
public static TunnelEntry createNewTunnel(
|
public static TunnelEntry createNewTunnel(
|
||||||
Context ctx,
|
Context ctx,
|
||||||
TunnelControllerGroup tcg,
|
TunnelControllerGroup tcg,
|
||||||
TunnelConfig cfg) {
|
TunnelConfig cfg) {
|
||||||
int tunnelId = tcg.getControllers().size();
|
int tunnelId = tcg.getControllers().size();
|
||||||
List<String> msgs = TunnelUtil.saveTunnel(
|
TunnelEntry ret = null;
|
||||||
I2PAppContext.getGlobalContext(), tcg, -1, cfg);
|
List<String> msgs = new ArrayList<>();
|
||||||
|
SaveTunnelTask task = new SaveTunnelTask(tcg, -1, cfg);
|
||||||
|
try {
|
||||||
|
msgs.addAll(task.execute().get());
|
||||||
|
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
||||||
|
ret = new TunnelEntry(ctx, cur, tunnelId);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Util.e("Interrupted while saving tunnel config", e);
|
||||||
|
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Util.e("Error while saving tunnel config", e);
|
||||||
|
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
|
||||||
|
}
|
||||||
// TODO: Do something else with the other messages.
|
// TODO: Do something else with the other messages.
|
||||||
Toast.makeText(ctx.getApplicationContext(),
|
Toast.makeText(ctx.getApplicationContext(),
|
||||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||||
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
return ret;
|
||||||
return new TunnelEntry(ctx, cur, tunnelId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TunnelEntry(Context context, TunnelController controller, int id) {
|
public TunnelEntry(Context context, TunnelController controller, int id) {
|
||||||
@ -262,6 +280,8 @@ public class TunnelEntry {
|
|||||||
return mContext.getResources()
|
return mContext.getResources()
|
||||||
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
||||||
case STARTING:
|
case STARTING:
|
||||||
|
return mContext.getResources()
|
||||||
|
.getDrawable(R.drawable.ic_sync_black_24dp);
|
||||||
case RUNNING:
|
case RUNNING:
|
||||||
case NOT_RUNNING:
|
case NOT_RUNNING:
|
||||||
default:
|
default:
|
||||||
|
@ -144,16 +144,13 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
|
|||||||
tvh.status.setBackgroundDrawable(tunnel.getStatusBackground());
|
tvh.status.setBackgroundDrawable(tunnel.getStatusBackground());
|
||||||
else
|
else
|
||||||
tvh.status.setBackground(tunnel.getStatusBackground());
|
tvh.status.setBackground(tunnel.getStatusBackground());
|
||||||
|
ViewCompat.setTransitionName(tvh.status,
|
||||||
|
"status" + tunnel.getId());
|
||||||
|
|
||||||
tvh.name.setText(tunnel.getName());
|
tvh.name.setText(tunnel.getName());
|
||||||
tvh.description.setText(tunnel.getDescription());
|
tvh.description.setText(tunnel.getDescription());
|
||||||
tvh.interfacePort.setText(tunnel.getTunnelLink(false));
|
tvh.interfacePort.setText(tunnel.getTunnelLink(false));
|
||||||
|
|
||||||
ViewCompat.setTransitionName(tvh.name,
|
|
||||||
mCtx.getString(R.string.TUNNEL_NAME) + tunnel.getId());
|
|
||||||
ViewCompat.setTransitionName(tvh.description,
|
|
||||||
mCtx.getString(R.string.TUNNEL_DESCRIPTION) + tunnel.getId());
|
|
||||||
|
|
||||||
tvh.itemView.setSelected(mTwoPane.isTwoPane() && position == mActivatedPosition);
|
tvh.itemView.setSelected(mTwoPane.isTwoPane() && position == mActivatedPosition);
|
||||||
tvh.itemView.setOnClickListener(new View.OnClickListener() {
|
tvh.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -162,13 +159,10 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
|
|||||||
mActivatedPosition = position;
|
mActivatedPosition = position;
|
||||||
notifyItemChanged(oldPosition);
|
notifyItemChanged(oldPosition);
|
||||||
notifyItemChanged(position);
|
notifyItemChanged(position);
|
||||||
Pair<View, String> namePair = Pair.create(
|
Pair<View, String> statusPair = Pair.create(
|
||||||
(View)tvh.name,
|
(View)tvh.status,
|
||||||
ViewCompat.getTransitionName(tvh.name));
|
ViewCompat.getTransitionName(tvh.status));
|
||||||
Pair<View, String> descPair = Pair.create(
|
Pair<View, String>[] pairs = new Pair[]{ statusPair};
|
||||||
(View)tvh.description,
|
|
||||||
ViewCompat.getTransitionName(tvh.description));
|
|
||||||
Pair<View, String>[] pairs = new Pair[]{ namePair, descPair};
|
|
||||||
mListener.onTunnelSelected(tunnel.getId(), pairs);
|
mListener.onTunnelSelected(tunnel.getId(), pairs);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -150,7 +150,11 @@ public class TunnelListFragment extends Fragment implements
|
|||||||
};
|
};
|
||||||
|
|
||||||
public void updateState(State state) {
|
public void updateState(State state) {
|
||||||
if (state == State.STOPPING || state == State.STOPPED ||
|
try {
|
||||||
|
if (state == State.INIT ||
|
||||||
|
state == State.STARTING || // Wait until RouterContext is initialised
|
||||||
|
state == State.STOPPING ||
|
||||||
|
state == State.STOPPED ||
|
||||||
state == State.MANUAL_STOPPING ||
|
state == State.MANUAL_STOPPING ||
|
||||||
state == State.MANUAL_STOPPED ||
|
state == State.MANUAL_STOPPED ||
|
||||||
state == State.MANUAL_QUITTING ||
|
state == State.MANUAL_QUITTING ||
|
||||||
@ -158,6 +162,9 @@ public class TunnelListFragment extends Fragment implements
|
|||||||
getLoaderManager().destroyLoader(mClientTunnels ? CLIENT_LOADER_ID : SERVER_LOADER_ID);
|
getLoaderManager().destroyLoader(mClientTunnels ? CLIENT_LOADER_ID : SERVER_LOADER_ID);
|
||||||
else
|
else
|
||||||
initTunnels();
|
initTunnels();
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Fragment isn't attached to any activity, so ignore state change
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initTunnels() {
|
private void initTunnels() {
|
||||||
|
@ -2,6 +2,7 @@ package net.i2p.android.i2ptunnel;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -21,7 +22,36 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DialogFragment onGetFinishWizardDialog() {
|
protected DialogFragment onGetFinishWizardDialog() {
|
||||||
return new DialogFragment() {
|
return FinishWizardDialogFragment.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFinishWizard() {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(TunnelsContainer.TUNNEL_WIZARD_DATA, mWizardModel.save());
|
||||||
|
setResult(Activity.RESULT_OK, result);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FinishWizardDialogFragment extends DialogFragment {
|
||||||
|
TunnelWizardActivity mListener;
|
||||||
|
|
||||||
|
public static DialogFragment newInstance() {
|
||||||
|
return new FinishWizardDialogFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
// Verify that the host fragment implements the callback interface
|
||||||
|
try {
|
||||||
|
// Instantiate the TunnelWizardActivity so we can send events to the host
|
||||||
|
mListener = (TunnelWizardActivity) context;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// The fragment doesn't implement the interface, throw exception
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must be TunnelWizardActivity");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
@ -31,16 +61,12 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
|
|||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
Intent result = new Intent();
|
|
||||||
result.putExtra(TunnelsContainer.TUNNEL_WIZARD_DATA, mWizardModel.save());
|
|
||||||
setResult(Activity.RESULT_OK, result);
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
finish();
|
mListener.onFinishWizard();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.create();
|
.create();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,14 @@ public class TunnelsContainer extends Fragment implements
|
|||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean showActions() {
|
||||||
|
RouterContext rCtx = Util.getRouterContext();
|
||||||
|
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||||
|
return rCtx != null && tcg != null &&
|
||||||
|
(tcg.getState() == ClientAppState.STARTING ||
|
||||||
|
tcg.getState() == ClientAppState.RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View v = inflater.inflate(R.layout.container_tunnels, container, false);
|
View v = inflater.inflate(R.layout.container_tunnels, container, false);
|
||||||
@ -72,6 +80,7 @@ public class TunnelsContainer extends Fragment implements
|
|||||||
mViewPager = (ViewPager) v.findViewById(R.id.pager);
|
mViewPager = (ViewPager) v.findViewById(R.id.pager);
|
||||||
mPageIndicator = (TitlePageIndicator) v.findViewById(R.id.page_indicator);
|
mPageIndicator = (TitlePageIndicator) v.findViewById(R.id.page_indicator);
|
||||||
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
|
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
|
||||||
|
mNewTunnel.setVisibility(showActions() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (v.findViewById(R.id.detail_fragment) != null) {
|
if (v.findViewById(R.id.detail_fragment) != null) {
|
||||||
// The detail container view will be present only in the
|
// The detail container view will be present only in the
|
||||||
@ -154,18 +163,17 @@ public class TunnelsContainer extends Fragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrepareOptionsMenu(Menu menu) {
|
public void onPrepareOptionsMenu(Menu menu) {
|
||||||
RouterContext rCtx = Util.getRouterContext();
|
boolean showActions = showActions();
|
||||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
|
||||||
boolean showActions = rCtx != null && tcg != null &&
|
|
||||||
(tcg.getState() == ClientAppState.STARTING ||
|
|
||||||
tcg.getState() == ClientAppState.RUNNING);
|
|
||||||
|
|
||||||
menu.findItem(R.id.action_start_all_tunnels).setVisible(showActions);
|
menu.findItem(R.id.action_start_all_tunnels).setVisible(showActions);
|
||||||
menu.findItem(R.id.action_stop_all_tunnels).setVisible(showActions);
|
menu.findItem(R.id.action_stop_all_tunnels).setVisible(showActions);
|
||||||
menu.findItem(R.id.action_restart_all_tunnels).setVisible(showActions);
|
menu.findItem(R.id.action_restart_all_tunnels).setVisible(showActions);
|
||||||
|
|
||||||
|
// Was causing a NPE in version 4745238 (0.9.31)
|
||||||
|
if (mNewTunnel != null) {
|
||||||
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
|
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
@ -183,7 +191,10 @@ public class TunnelsContainer extends Fragment implements
|
|||||||
msgs = tcg.stopAllControllers();
|
msgs = tcg.stopAllControllers();
|
||||||
break;
|
break;
|
||||||
case R.id.action_restart_all_tunnels:
|
case R.id.action_restart_all_tunnels:
|
||||||
msgs = tcg.restartAllControllers();
|
// Do a manual stop-start cycle, because tcg.restartAllControllers() happens in the
|
||||||
|
// foreground, whereas tcg.startAllControllers() fires off threads for starting.
|
||||||
|
msgs = tcg.stopAllControllers();
|
||||||
|
msgs.addAll(tcg.startAllControllers());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
@ -205,6 +216,7 @@ public class TunnelsContainer extends Fragment implements
|
|||||||
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), tcg, tunnelData);
|
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), tcg, tunnelData);
|
||||||
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), tcg, cfg);
|
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), tcg, cfg);
|
||||||
|
|
||||||
|
if (tunnel != null) {
|
||||||
if (tunnel.isClient() && mClientFrag != null)
|
if (tunnel.isClient() && mClientFrag != null)
|
||||||
mClientFrag.addTunnel(tunnel);
|
mClientFrag.addTunnel(tunnel);
|
||||||
else if (mServerFrag != null)
|
else if (mServerFrag != null)
|
||||||
@ -212,6 +224,7 @@ public class TunnelsContainer extends Fragment implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
@ -5,14 +5,18 @@ import android.os.Bundle;
|
|||||||
import android.support.v7.preference.Preference;
|
import android.support.v7.preference.Preference;
|
||||||
import android.support.v7.preference.PreferenceGroup;
|
import android.support.v7.preference.PreferenceGroup;
|
||||||
import android.support.v7.preference.PreferenceScreen;
|
import android.support.v7.preference.PreferenceScreen;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
|
||||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
import net.i2p.android.preferences.util.CustomPreferenceFragment;
|
import net.i2p.android.preferences.util.CustomPreferenceFragment;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
|
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
|
||||||
protected static final String ARG_TUNNEL_ID = "tunnelId";
|
protected static final String ARG_TUNNEL_ID = "tunnelId";
|
||||||
|
|
||||||
@ -31,13 +35,30 @@ public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragm
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mGroup == null) {
|
if (mGroup == null) {
|
||||||
// TODO Show error
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
error, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
|
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
|
||||||
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
|
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
|
||||||
|
try {
|
||||||
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
|
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Tunnel doesn't exist, or the tunnel config file could not be read
|
||||||
|
Util.e("Could not load tunnel details", e);
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
|
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
|
||||||
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
|
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
|
||||||
|
try {
|
||||||
loadPreferences();
|
loadPreferences();
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
// mGroup couldn't load its config file
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
iae.toString(), Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +83,17 @@ public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragm
|
|||||||
private void saveTunnel() {
|
private void saveTunnel() {
|
||||||
if (mGroup != null) {
|
if (mGroup != null) {
|
||||||
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
|
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
|
||||||
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
|
SaveTunnelTask task = new SaveTunnelTask(mGroup, mTunnelId, cfg);
|
||||||
|
try {
|
||||||
|
// TODO: There used to be a possible ANR here, because the underlying I2P code
|
||||||
|
// checks if the session is open as part of updating its config. We may need to save
|
||||||
|
// completely asynchronously (and ensure we do actually save before the app closes).
|
||||||
|
task.execute().get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Util.e("Interrupted while saving tunnel config", e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Util.e("Error while saving tunnel config", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.i2p.android.i2ptunnel.preferences;
|
package net.i2p.android.i2ptunnel.preferences;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
@ -135,7 +136,9 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
|||||||
protected Void doInBackground(Void... voids) {
|
protected Void doInBackground(Void... voids) {
|
||||||
Set<String> interfaceSet = Addresses.getAllAddresses();
|
Set<String> interfaceSet = Addresses.getAllAddresses();
|
||||||
final String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
|
final String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
Activity activity = getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
reachableBy.setEntries(interfaces);
|
reachableBy.setEntries(interfaces);
|
||||||
@ -143,6 +146,7 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
|||||||
reachableBy.setEnabled(true);
|
reachableBy.setEnabled(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package net.i2p.android.i2ptunnel.util;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a TunnelConfig.
|
||||||
|
*
|
||||||
|
* This must be performed in a background thread, because the underlying I2P code calls
|
||||||
|
* InetAddress.getByName(), which will trigger a NetworkOnMainThreadException otherwise.
|
||||||
|
*/
|
||||||
|
public class SaveTunnelTask extends AsyncTask<Void, Void, List<String>> {
|
||||||
|
TunnelControllerGroup mGroup;
|
||||||
|
int mTunnelId;
|
||||||
|
TunnelConfig mCfg;
|
||||||
|
|
||||||
|
public SaveTunnelTask(TunnelControllerGroup group, int tunnelId, TunnelConfig cfg) {
|
||||||
|
mGroup = group;
|
||||||
|
mTunnelId = tunnelId;
|
||||||
|
mCfg = cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> doInBackground(Void... voids) {
|
||||||
|
return TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, mCfg);
|
||||||
|
}
|
||||||
|
}
|
@ -68,6 +68,10 @@ public class ConnectionLimitPreference extends EditTextPreference {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean persistString(String value) {
|
protected boolean persistString(String value) {
|
||||||
|
try {
|
||||||
return value != null && persistInt(Integer.valueOf(value));
|
return value != null && persistInt(Integer.valueOf(value));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,10 @@ public class IntEditTextPreference extends EditTextPreference {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean persistString(String value) {
|
protected boolean persistString(String value) {
|
||||||
|
try {
|
||||||
return value != null && persistInt(Integer.valueOf(value));
|
return value != null && persistInt(Integer.valueOf(value));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,13 +63,13 @@ public class ConsoleContainer extends Fragment {
|
|||||||
startActivity(graphs);
|
startActivity(graphs);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mConsoleMenu.findViewById(R.id.action_peers).setOnClickListener(new View.OnClickListener() {
|
// mConsoleMenu.findViewById(R.id.action_peers).setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
// @Override
|
||||||
public void onClick(View view) {
|
// public void onClick(View view) {
|
||||||
Intent peers = new Intent(getActivity(), PeersActivity.class);
|
// Intent peers = new Intent(getActivity(), PeersActivity.class);
|
||||||
startActivity(peers);
|
// startActivity(peers);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
mConsoleMenu.findViewById(R.id.action_netdb).setOnClickListener(new View.OnClickListener() {
|
mConsoleMenu.findViewById(R.id.action_netdb).setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
@ -87,11 +87,6 @@ public class ConsoleContainer extends Fragment {
|
|||||||
inflater.inflate(R.menu.activity_main_actions, menu);
|
inflater.inflate(R.menu.activity_main_actions, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareOptionsMenu(Menu menu) {
|
|
||||||
setMenuVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setMenuVisibility() {
|
private void setMenuVisibility() {
|
||||||
boolean routerRunning = Util.getRouterContext() != null;
|
boolean routerRunning = Util.getRouterContext() != null;
|
||||||
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
|
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
|
||||||
@ -100,8 +95,8 @@ public class ConsoleContainer extends Fragment {
|
|||||||
if (getActivity() != null) {
|
if (getActivity() != null) {
|
||||||
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||||
.getBoolean("i2pandroid.main.showStats", false);
|
.getBoolean("i2pandroid.main.showStats", false);
|
||||||
mConsoleMenu.findViewById(R.id.action_peers).setVisibility(
|
// mConsoleMenu.findViewById(R.id.action_peers).setVisibility(
|
||||||
advanced && routerRunning ? View.VISIBLE : View.GONE);
|
// advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||||
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(
|
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(
|
||||||
advanced && routerRunning ? View.VISIBLE : View.GONE);
|
advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -531,7 +531,7 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
|
|
||||||
public AlphaComparator(RouterContext ctx) {
|
public AlphaComparator(RouterContext ctx) {
|
||||||
_ctx = ctx;
|
_ctx = ctx;
|
||||||
xsc = _(ctx, SHARED_CLIENTS);
|
xsc = _t(ctx, SHARED_CLIENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compare(Destination lhs, Destination rhs) {
|
public int compare(Destination lhs, Destination rhs) {
|
||||||
@ -559,12 +559,12 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
if (name == null)
|
if (name == null)
|
||||||
name = d.calculateHash().toBase64().substring(0, 6);
|
name = d.calculateHash().toBase64().substring(0, 6);
|
||||||
else
|
else
|
||||||
name = _(ctx, name);
|
name = _t(ctx, name);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String _(RouterContext ctx, String s) {
|
private String _t(RouterContext ctx, String s) {
|
||||||
if (SHARED_CLIENTS.equals(s))
|
if (SHARED_CLIENTS.equals(s))
|
||||||
return getString(R.string.shared_clients);
|
return getString(R.string.shared_clients);
|
||||||
else
|
else
|
||||||
@ -648,9 +648,11 @@ public class MainFragment extends I2PFragmentBase {
|
|||||||
private void checkFirstStart() {
|
private void checkFirstStart() {
|
||||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
|
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
|
||||||
if (firstStart) {
|
// Check ab.isFinishing() because DialogFragment.show() will throw IllegalStateException if
|
||||||
|
// called after ab.onSaveInstanceState().
|
||||||
|
if (firstStart && !ab.isFinishing()) {
|
||||||
FirstStartDialog dialog = new FirstStartDialog();
|
FirstStartDialog dialog = new FirstStartDialog();
|
||||||
dialog.show(getActivity().getSupportFragmentManager(), "firststart");
|
dialog.show(ab.getSupportFragmentManager(), "firststart");
|
||||||
ab.setPref(PREF_FIRST_START, false);
|
ab.setPref(PREF_FIRST_START, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.i2p.android.router.addressbook;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -20,7 +21,36 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DialogFragment onGetFinishWizardDialog() {
|
protected DialogFragment onGetFinishWizardDialog() {
|
||||||
return new DialogFragment() {
|
return FinishWizardDialogFragment.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFinishWizard() {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(AddressbookContainer.ADD_WIZARD_DATA, mWizardModel.save());
|
||||||
|
setResult(Activity.RESULT_OK, result);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FinishWizardDialogFragment extends DialogFragment {
|
||||||
|
AddressbookAddWizardActivity mListener;
|
||||||
|
|
||||||
|
public static DialogFragment newInstance() {
|
||||||
|
return new FinishWizardDialogFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
// Verify that the host fragment implements the callback interface
|
||||||
|
try {
|
||||||
|
// Instantiate the AddressbookAddWizardActivity so we can send events to the host
|
||||||
|
mListener = (AddressbookAddWizardActivity) context;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// The fragment doesn't implement the interface, throw exception
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must be AddressbookAddWizardActivity");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
@ -30,16 +60,12 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
|
|||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
Intent result = new Intent();
|
|
||||||
setResult(Activity.RESULT_OK, result);
|
|
||||||
result.putExtra(AddressbookContainer.ADD_WIZARD_DATA, mWizardModel.save());
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
finish();
|
mListener.onFinishWizard();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.create();
|
.create();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import com.eowise.recyclerview.stickyheaders.StickyHeadersBuilder;
|
|||||||
import com.eowise.recyclerview.stickyheaders.StickyHeadersItemDecoration;
|
import com.eowise.recyclerview.stickyheaders.StickyHeadersItemDecoration;
|
||||||
import com.pnikosis.materialishprogress.ProgressWheel;
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
|
|
||||||
import net.i2p.addressbook.Daemon;
|
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
import net.i2p.android.router.service.RouterService;
|
import net.i2p.android.router.service.RouterService;
|
||||||
import net.i2p.android.router.service.State;
|
import net.i2p.android.router.service.State;
|
||||||
@ -167,15 +166,23 @@ public class AddressbookFragment extends Fragment implements
|
|||||||
int loaderId = PRIVATE_BOOK.equals(mBook) ?
|
int loaderId = PRIVATE_BOOK.equals(mBook) ?
|
||||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID;
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID;
|
||||||
|
|
||||||
if (state == State.STOPPING || state == State.STOPPED ||
|
try {
|
||||||
|
LoaderManager manager = getLoaderManager();
|
||||||
|
if (state == State.INIT ||
|
||||||
|
state == State.STARTING || // Wait until RouterContext is initialised
|
||||||
|
state == State.STOPPING ||
|
||||||
|
state == State.STOPPED ||
|
||||||
state == State.MANUAL_STOPPING ||
|
state == State.MANUAL_STOPPING ||
|
||||||
state == State.MANUAL_STOPPED ||
|
state == State.MANUAL_STOPPED ||
|
||||||
state == State.MANUAL_QUITTING ||
|
state == State.MANUAL_QUITTING ||
|
||||||
state == State.MANUAL_QUITTED)
|
state == State.MANUAL_QUITTED)
|
||||||
getLoaderManager().destroyLoader(loaderId);
|
manager.destroyLoader(loaderId);
|
||||||
else {
|
else {
|
||||||
mRecyclerView.setLoading(true);
|
mRecyclerView.setLoading(true);
|
||||||
getLoaderManager().initLoader(loaderId, null, this);
|
manager.initLoader(loaderId, null, this);
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Fragment isn't attached to any activity, so ignore state change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,8 +214,11 @@ public class AddressbookFragment extends Fragment implements
|
|||||||
mAddToAddressbook.setVisibility(rCtx == null ? View.GONE : View.VISIBLE);
|
mAddToAddressbook.setVisibility(rCtx == null ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
// Only show "Reload subscriptions" for router addressbook
|
// Only show "Reload subscriptions" for router addressbook
|
||||||
menu.findItem(R.id.action_reload_subscriptions).setVisible(
|
MenuItem reloadSubs = menu.findItem(R.id.action_reload_subscriptions);
|
||||||
|
if (reloadSubs != null) {
|
||||||
|
reloadSubs.setVisible(
|
||||||
rCtx != null && !PRIVATE_BOOK.equals(mBook));
|
rCtx != null && !PRIVATE_BOOK.equals(mBook));
|
||||||
|
}
|
||||||
|
|
||||||
// Only allow adding to private book
|
// Only allow adding to private book
|
||||||
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
|
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
|
||||||
@ -223,9 +233,12 @@ public class AddressbookFragment extends Fragment implements
|
|||||||
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_reload_subscriptions:
|
case R.id.action_reload_subscriptions:
|
||||||
Daemon.wakeup();
|
RouterContext rCtx = Util.getRouterContext();
|
||||||
|
if (rCtx != null) {
|
||||||
|
rCtx.namingService().requestUpdate(null);
|
||||||
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
|
@ -61,7 +61,7 @@ public class AddressbookSettingsActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private boolean load() {
|
private boolean load() {
|
||||||
String res = FileUtil.readTextFile(i2pDir.getAbsolutePath(), -1, true);
|
String res = FileUtil.readTextFile(i2pDir.getAbsolutePath(), -1, true);
|
||||||
if (res.length() > 0) {
|
if (res != null && res.length() > 0) {
|
||||||
text_content_subscriptions.setText(res);
|
text_content_subscriptions.setText(res);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ import net.i2p.data.DataHelper;
|
|||||||
import net.i2p.router.Job;
|
import net.i2p.router.Job;
|
||||||
import net.i2p.router.Router;
|
import net.i2p.router.Router;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.RouterLaunch;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
@ -213,16 +212,18 @@ public class RouterService extends Service {
|
|||||||
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
|
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
|
||||||
|
|
||||||
// Launch the router!
|
// Launch the router!
|
||||||
RouterLaunch.main(null);
|
// TODO Store this somewhere instead of relying on global context?
|
||||||
|
Router r = new Router();
|
||||||
|
r.runRouter();
|
||||||
synchronized(_stateLock) {
|
synchronized(_stateLock) {
|
||||||
if(_state != State.STARTING) {
|
if(_state != State.STARTING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(State.RUNNING);
|
setState(State.RUNNING);
|
||||||
_statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_running);
|
_statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_running);
|
||||||
_context = Util.getRouterContext();
|
_context = r.getContext();
|
||||||
if (_context == null) {
|
if (_context == null) {
|
||||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
throw new IllegalStateException("Router has no context?");
|
||||||
}
|
}
|
||||||
_context.router().setKillVMOnEnd(false);
|
_context.router().setKillVMOnEnd(false);
|
||||||
Job loadJob = new LoadClientsJob(RouterService.this, _context, _notif);
|
Job loadJob = new LoadClientsJob(RouterService.this, _context, _notif);
|
||||||
|
@ -39,7 +39,7 @@ public class PeersFragment extends I2PFragmentBase {
|
|||||||
wv.getSettings().setLoadsImagesAutomatically(true); // was false
|
wv.getSettings().setLoadsImagesAutomatically(true); // was false
|
||||||
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||||
wv.getSettings().setUseWideViewPort(true);
|
wv.getSettings().setUseWideViewPort(true);
|
||||||
_wvClient = new I2PWebViewClient();
|
_wvClient = new I2PWebViewClient(this);
|
||||||
wv.setWebViewClient(_wvClient);
|
wv.setWebViewClient(_wvClient);
|
||||||
wv.getSettings().setBuiltInZoomControls(true);
|
wv.getSettings().setBuiltInZoomControls(true);
|
||||||
return v;
|
return v;
|
||||||
|
@ -299,7 +299,15 @@ public abstract class Util implements I2PConstants {
|
|||||||
|
|
||||||
public static String getFileDir(Context context) {
|
public static String getFileDir(Context context) {
|
||||||
// This needs to be changed so that we can have an alternative place
|
// This needs to be changed so that we can have an alternative place
|
||||||
return context.getFilesDir().getAbsolutePath();
|
File f = context.getFilesDir();
|
||||||
|
if (f == null) {
|
||||||
|
// https://code.google.com/p/android/issues/detail?id=8886
|
||||||
|
// Seems to be a race condition; try again.
|
||||||
|
// Supposedly only in pre-4.4 devices, but this was observed on a
|
||||||
|
// Samsung Galaxy Grand Prime (grandprimeve3g), 1024MB RAM, Android 5.1
|
||||||
|
f = context.getFilesDir();
|
||||||
|
}
|
||||||
|
return f.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package net.i2p.android.router.web;
|
package net.i2p.android.router.web;
|
||||||
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.webkit.HttpAuthHandler;
|
import android.webkit.HttpAuthHandler;
|
||||||
@ -29,6 +31,7 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
public class I2PWebViewClient extends WebViewClient {
|
public class I2PWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
|
private Fragment _parentFrag;
|
||||||
private BGLoad _lastTask;
|
private BGLoad _lastTask;
|
||||||
/** save it here so we can dismiss it in onPageFinished() */
|
/** save it here so we can dismiss it in onPageFinished() */
|
||||||
private ProgressDialog _lastDialog;
|
private ProgressDialog _lastDialog;
|
||||||
@ -40,6 +43,11 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
private static final String ERROR_URL = "<p>Unable to load URL: ";
|
private static final String ERROR_URL = "<p>Unable to load URL: ";
|
||||||
private static final String ERROR_ROUTER = "<p>Your router (or the HTTP proxy) does not appear to be running.</p>";
|
private static final String ERROR_ROUTER = "<p>Your router (or the HTTP proxy) does not appear to be running.</p>";
|
||||||
|
|
||||||
|
public I2PWebViewClient(Fragment parentFrag) {
|
||||||
|
super();
|
||||||
|
_parentFrag = parentFrag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
Util.d("Should override? " + url);
|
Util.d("Should override? " + url);
|
||||||
@ -102,7 +110,7 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
///////// API 8
|
///////// API 8
|
||||||
// Otherwise hangs waiting for CSS
|
// Otherwise hangs waiting for CSS
|
||||||
view.getSettings().setBlockNetworkLoads(false);
|
view.getSettings().setBlockNetworkLoads(false);
|
||||||
_lastDialog = new ProgressDialog(view.getContext());
|
_lastDialog = new ProgressDialog(_parentFrag.getContext());
|
||||||
BGLoad task = new BackgroundEepLoad(view, h, _lastDialog);
|
BGLoad task = new BackgroundEepLoad(view, h, _lastDialog);
|
||||||
_lastTask = task;
|
_lastTask = task;
|
||||||
task.execute(url);
|
task.execute(url);
|
||||||
@ -215,10 +223,12 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
|
|
||||||
private abstract static class BGLoad extends AsyncTask<String, Integer, Integer> implements DialogInterface.OnCancelListener {
|
private abstract static class BGLoad extends AsyncTask<String, Integer, Integer> implements DialogInterface.OnCancelListener {
|
||||||
protected final WebView _view;
|
protected final WebView _view;
|
||||||
|
protected final Context _ctx;
|
||||||
protected final ProgressDialog _dialog;
|
protected final ProgressDialog _dialog;
|
||||||
|
|
||||||
public BGLoad(WebView view, ProgressDialog dialog) {
|
public BGLoad(WebView view, ProgressDialog dialog) {
|
||||||
_view = view;
|
_view = view;
|
||||||
|
_ctx = view.getContext();
|
||||||
if (dialog != null)
|
if (dialog != null)
|
||||||
dialog.setCancelable(true);
|
dialog.setCancelable(true);
|
||||||
_dialog = dialog;
|
_dialog = dialog;
|
||||||
@ -305,9 +315,9 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
protected Integer doInBackground(String... urls) {
|
protected Integer doInBackground(String... urls) {
|
||||||
final String url = urls[0];
|
final String url = urls[0];
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
File cacheFile = AppCache.getInstance(_view.getContext()).getCacheFile(uri);
|
File cacheFile = AppCache.getInstance(_ctx).getCacheFile(uri);
|
||||||
if (cacheFile.exists()) {
|
if (cacheFile.exists()) {
|
||||||
final Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
|
final Uri resUri = AppCache.getInstance(_ctx).getCacheUri(uri);
|
||||||
Util.d("Loading " + url + " from resource cache " + resUri);
|
Util.d("Loading " + url + " from resource cache " + resUri);
|
||||||
_view.post(new Runnable() {
|
_view.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -325,7 +335,7 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
//EepGetFetcher fetcher = new EepGetFetcher(url);
|
//EepGetFetcher fetcher = new EepGetFetcher(url);
|
||||||
OutputStream out = null;
|
OutputStream out = null;
|
||||||
try {
|
try {
|
||||||
out = AppCache.getInstance(_view.getContext()).createCacheFile(uri);
|
out = AppCache.getInstance(_ctx).createCacheFile(uri);
|
||||||
// write error to stream
|
// write error to stream
|
||||||
EepGetFetcher fetcher = new EepGetFetcher(url, out, true);
|
EepGetFetcher fetcher = new EepGetFetcher(url, out, true);
|
||||||
fetcher.addStatusListener(this);
|
fetcher.addStatusListener(this);
|
||||||
@ -338,11 +348,11 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
if (success) {
|
if (success) {
|
||||||
// store in cache, get content URL, and load that way
|
// store in cache, get content URL, and load that way
|
||||||
// Set as current base
|
// Set as current base
|
||||||
final Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri, true);
|
final Uri content = AppCache.getInstance(_ctx).addCacheFile(uri, true);
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
Util.d("Stored cache in " + content);
|
Util.d("Stored cache in " + content);
|
||||||
} else {
|
} else {
|
||||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
AppCache.getInstance(_ctx).removeCacheFile(uri);
|
||||||
Util.d("cache create error");
|
Util.d("cache create error");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -381,7 +391,7 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
AppCache.getInstance(_ctx).removeCacheFile(uri);
|
||||||
Util.d("loading error data URL: " + url);
|
Util.d("loading error data URL: " + url);
|
||||||
final String finalMsg = msg;
|
final String finalMsg = msg;
|
||||||
_view.post(new Runnable() {
|
_view.post(new Runnable() {
|
||||||
@ -403,6 +413,7 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
protected void onProgressUpdate(Integer... progress) {
|
protected void onProgressUpdate(Integer... progress) {
|
||||||
if (isCancelled())
|
if (isCancelled())
|
||||||
return;
|
return;
|
||||||
|
try {
|
||||||
int prog = progress[0];
|
int prog = progress[0];
|
||||||
if (prog < 0) {
|
if (prog < 0) {
|
||||||
_dialog.setTitle("Contacting...");
|
_dialog.setTitle("Contacting...");
|
||||||
@ -428,6 +439,10 @@ public class I2PWebViewClient extends WebViewClient {
|
|||||||
} else {
|
} else {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
// throws IAE - not attached to window manager - perhaps due to screen rotation?
|
||||||
|
Util.e("Error while updating I2PWebViewClient dialog", iae);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -44,7 +44,7 @@ public class WebFragment extends I2PFragmentBase {
|
|||||||
TextView tv = (TextView) v.findViewById(R.id.browser_status);
|
TextView tv = (TextView) v.findViewById(R.id.browser_status);
|
||||||
tv.setText(WARNING);
|
tv.setText(WARNING);
|
||||||
WebView wv = (WebView) v.findViewById(R.id.browser_webview);
|
WebView wv = (WebView) v.findViewById(R.id.browser_webview);
|
||||||
_wvClient = new I2PWebViewClient();
|
_wvClient = new I2PWebViewClient(this);
|
||||||
wv.setWebViewClient(_wvClient);
|
wv.setWebViewClient(_wvClient);
|
||||||
wv.getSettings().setBuiltInZoomControls(true);
|
wv.getSettings().setBuiltInZoomControls(true);
|
||||||
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||||
|
@ -31,6 +31,7 @@ public class SingleTextFieldPage extends Page {
|
|||||||
protected String mDef = null;
|
protected String mDef = null;
|
||||||
protected String mDesc = "";
|
protected String mDesc = "";
|
||||||
protected boolean mNumeric = false;
|
protected boolean mNumeric = false;
|
||||||
|
private String mFeedback;
|
||||||
|
|
||||||
public SingleTextFieldPage(ModelCallbacks callbacks, String title) {
|
public SingleTextFieldPage(ModelCallbacks callbacks, String title) {
|
||||||
super(callbacks, title);
|
super(callbacks, title);
|
||||||
@ -81,14 +82,24 @@ public class SingleTextFieldPage extends Page {
|
|||||||
// Override these in subclasses to add content verification.
|
// Override these in subclasses to add content verification.
|
||||||
|
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
|
if (mNumeric) {
|
||||||
|
try {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
Integer.parseInt(mData.getString(SIMPLE_DATA_KEY));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
mFeedback = "Not a number";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mFeedback = "";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean showFeedback() {
|
public boolean showFeedback() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFeedback() {
|
public String getFeedback() {
|
||||||
return "";
|
return mFeedback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,12 @@ public abstract class AbstractWizardActivity extends FragmentActivity implements
|
|||||||
// Create the WizardModel before super.onCreate() in case a Fragment
|
// Create the WizardModel before super.onCreate() in case a Fragment
|
||||||
// is created and tries to call e.g. onGetPage()
|
// is created and tries to call e.g. onGetPage()
|
||||||
mWizardModel = onCreateModel();
|
mWizardModel = onCreateModel();
|
||||||
if (savedInstanceState != null)
|
if (savedInstanceState != null) {
|
||||||
mWizardModel.load(savedInstanceState.getBundle("model"));
|
Bundle model = savedInstanceState.getBundle("model");
|
||||||
|
if (model != null) {
|
||||||
|
mWizardModel.load(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_wizard);
|
setContentView(R.layout.activity_wizard);
|
||||||
|
@ -190,6 +190,10 @@ public class I2PB64DestinationFragment extends Fragment {
|
|||||||
Util.e("Could not find B64 file", fnfe);
|
Util.e("Could not find B64 file", fnfe);
|
||||||
Toast.makeText(getActivity(), "Could not find B64 file.",
|
Toast.makeText(getActivity(), "Could not find B64 file.",
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
Util.e("Could not open B64 file", se);
|
||||||
|
Toast.makeText(getActivity(), "Could not open B64 file.",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
} finally {
|
} finally {
|
||||||
if (br != null)
|
if (br != null)
|
||||||
try {
|
try {
|
||||||
|
Before Width: | Height: | Size: 223 B |
Before Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 423 B |
Before Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 246 B |
Before Width: | Height: | Size: 535 B |
Before Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 223 B |
Before Width: | Height: | Size: 283 B |
Before Width: | Height: | Size: 430 B |
Before Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 282 B |
Before Width: | Height: | Size: 531 B |
Before Width: | Height: | Size: 650 B |
Before Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 479 B |
Before Width: | Height: | Size: 641 B |
Before Width: | Height: | Size: 767 B |
Before Width: | Height: | Size: 331 B |
Before Width: | Height: | Size: 640 B |
Before Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 652 B |
Before Width: | Height: | Size: 806 B |
Before Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 240 B |
Before Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 314 B |
Before Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 421 B |
Before Width: | Height: | Size: 319 B |
Before Width: | Height: | Size: 211 B |
Before Width: | Height: | Size: 223 B |
Before Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 168 B |
Before Width: | Height: | Size: 234 B |
Before Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 346 B |
Before Width: | Height: | Size: 454 B |
Before Width: | Height: | Size: 346 B |
Before Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 260 B |
Before Width: | Height: | Size: 444 B |
Before Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 533 B |
Before Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 444 B |
Before Width: | Height: | Size: 402 B |
Before Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 330 B |
Before Width: | Height: | Size: 378 B |
Before Width: | Height: | Size: 270 B |
Before Width: | Height: | Size: 737 B |
Before Width: | Height: | Size: 530 B |
Before Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 494 B |