Commit 862e9144 authored by bixing's avatar bixing

添加ADMOB媒体包

parent 43cf7c35
/build
\ No newline at end of file
plugins {
id 'com.android.application'
}
android {
namespace 'com.ads.cal.picturetranslate'
compileSdk 34
defaultConfig {
applicationId "com.hr.heart.rate"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
}
buildTypes {
release {
debuggable false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'app-proguard-rules.pro'
}
}
buildFeatures {
aidl = true
viewBinding = true
dataBinding = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
signingConfigs {
toolsTest {
storeFile file("picturetranslate.jks")
storePassword '123456'
keyAlias 'key0'
keyPassword '123456'
}
testSign {
storeFile file("cc.jks")
storePassword '1#q!23w'
keyAlias 'key0'
keyPassword '1#q!23w'
}
}
flavorDimensions "versionName"
productFlavors {
transalte {
applicationId "picturetranslate.extractor.text.lite"
versionName "1.0.0.0"
signingConfig signingConfigs.toolsTest
manifestPlaceholders = [google_ad_app_id: "ca-app-pub-9356757350640102~6934070860", signature: "04ced0bb5c9f0b2adf2f27dd57538cc8", flavor_name: "GemHeart", authorities: "$applicationId-media-AProvider", media_version: "3"
]
}
ceshi {
versionCode 90015
versionName "1.0.15"
signingConfig signingConfigs.testSign
applicationId "com.hr.heart.rate"
manifestPlaceholders = [google_ad_app_id: "ca-app-pub-3940256099942544~3347511713", signature: "8112af66991cd22a83219a1fd10403c9", flavor_name: "GemHeart", authorities: "$applicationId-media-AProvider", media_version: "3"
]
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'armeabi'
}
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.camera:camera-view:1.2.3'
implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation (platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
implementation 'com.or.ange:likepal:2.2.2'
// To recognize Latin script
implementation 'com.google.mlkit:text-recognition:16.0.0'
// To recognize Chinese script
implementation 'com.google.mlkit:text-recognition-chinese:16.0.0'
// To recognize Devanagari script
implementation 'com.google.mlkit:text-recognition-devanagari:16.0.0'
// To recognize Japanese script
implementation 'com.google.mlkit:text-recognition-japanese:16.0.0'
// To recognize Korean script
implementation 'com.google.mlkit:text-recognition-korean:16.0.0'
implementation "androidx.camera:camera-core:1.3.0-alpha04"
implementation "androidx.camera:camera-camera2:1.3.0-alpha04"
implementation "androidx.camera:camera-lifecycle:1.3.0-alpha04"
//图像裁剪
implementation 'com.edmodo:cropper:1.0.1'
implementation 'com.github.bumptech.glide:glide:4.15.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.27'
implementation("org.greenrobot:eventbus:3.3.1")
// Import the BoM for the Firebase platform
// implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
// Add the dependency for the Analytics library
// When using the BoM, you don't specify versions in Firebase library dependencies
// implementation("com.google.firebase:firebase-analytics")
// implementation 'com.google.android.gms:play-services-ads:22.5.0'
implementation 'com.google.guava:guava:27.0.1-android'
api "com.google.android.gms:play-services-ads-identifier:18.0.1"
api "com.google.android.gms:play-services-ads:24.0.0"
api('androidx.lifecycle:lifecycle-livedata:2.5.1')
api('androidx.lifecycle:lifecycle-viewmodel:2.5.1')
api('com.alibaba:fastjson:1.1.72.android')
api('io.reactivex.rxjava3:rxandroid:3.0.2')
api("me.leolin:ShortcutBadger:1.1.22@aar")
api('com.guolindev.permissionx:permissionx:1.7.1')
api "com.google.code.gson:gson:2.10.1"
//glide
api('jp.wasabeef:glide-transformations:4.3.0')
annotationProcessor("com.github.bumptech.glide:compiler:4.13.2")
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
/*UI相关第三方*/
//Adapter
api 'io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.14'
//Custom Ratingbar
implementation 'com.github.wdsqjq:AndRatingBar:1.0.6'
api 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
api "com.airbnb.android:lottie:6.1.0"
implementation "com.blankj:utilcodex:1.31.1"
implementation 'com.tencent.bugly:crashreport:latest.release'
api('androidx.lifecycle:lifecycle-livedata:2.5.1')
api('androidx.lifecycle:lifecycle-viewmodel:2.5.1')
api('com.alibaba:fastjson:1.1.72.android')
api('io.reactivex.rxjava3:rxandroid:3.0.2')
api("me.leolin:ShortcutBadger:1.1.22@aar")
api('com.guolindev.permissionx:permissionx:1.7.1')
// api config.libARouter
api "com.google.code.gson:gson:2.10.1"
//glide
api('jp.wasabeef:glide-transformations:4.3.0')
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
/*UI相关第三方*/
//Adapter
api 'io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.14'
//List Divider
// api 'com.aches.plugins:recyclerview-divider:1.0.1'
//Custom Ratingbar
api 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
api('com.airbnb.android:lottie:6.1.0')
implementation 'org.greenrobot:eventbus:3.1.1'
api('com.blankj:utilcodex:1.31.1')
api('com.google.android.material:material:1.8.0')
api('androidx.constraintlayout:constraintlayout:2.1.4')
api('androidx.lifecycle:lifecycle-livedata:2.5.1')
api('androidx.lifecycle:lifecycle-viewmodel:2.5.1')
api('com.android.volley:volley:1.2.1')
api("com.squareup.retrofit2:retrofit:2.5.0")
api("com.squareup.retrofit2:converter-gson:2.0.2")
api("com.squareup.okhttp3:logging-interceptor:3.8.1")
api("com.squareup.okhttp3:okhttp:3.12.0")
}
\ No newline at end of file
This diff is collapsed.
package com.ads.cal.picturetranslate;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.ads.cal.picturetranslate", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ads.cal.picturetranslate">
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<queries>
<intent>
<action android:name="com.media.am.intent.action.BIND_SERVICE"/>
</intent>
</queries>
<application
android:name="com.ads.cal.picturetranslate.PictureApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.PictureTranslate"
tools:targetApi="31">
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="ocr,ocr_chinese,ocr_devanagari,ocr_japanese,ocr_korean" />
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="${google_ad_app_id}" />
<activity
android:name="com.ads.cal.picturetranslate.activity.PictureTranslateStartActivity"
android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<data android:host="mainactivity" android:scheme="cn.fly2think.activity" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.ads.cal.picturetranslate.activity.AboutActivity"
android:exported="true"
android:screenOrientation="portrait"
/>
<activity
android:name="com.ads.cal.picturetranslate.activity.PictureTranslateListMainActivity"
android:exported="true"
android:configChanges="keyboardHidden|screenSize|screenLayout|orientation"
android:excludeFromRecents="true"
android:noHistory="true"
android:windowSoftInputMode="adjustResize"
android:icon="@android:drawable/divider_horizontal_bright"
android:label="@string/heart_lable"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="xsplash"
android:scheme="std" />
</intent-filter>
</activity>
<activity
android:name="com.ads.cal.picturetranslate.activity.CameraActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="true"
android:screenOrientation="behind"
android:hardwareAccelerated="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="cln"
android:scheme="clstdn" />
</intent-filter>
</activity>
<activity
android:name="com.ads.cal.picturetranslate.activity.PictureTranslateCutOutPhotoActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="true"
android:hardwareAccelerated="true" />
<activity
android:name="com.ads.cal.picturetranslate.activity.PictureTranslateResultActivity"
android:configChanges="keyboardHidden|screenSize|screenLayout|orientation"
android:excludeFromRecents="true"
android:exported="true"
android:icon="@android:drawable/divider_horizontal_bright"
android:label="@string/heart_lable"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
android:screenOrientation="behind">
</activity>
<activity
android:name="com.ads.cal.picturetranslate.activity.PictureTranslateShowPhotoActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait"
android:exported="true" />
<activity
android:name="com.ads.cal.picturetranslate.activity.PrivacyPolicyActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait"
android:exported="true" />
<meta-data
android:name="media_signature"
android:value="${signature}" />
<meta-data
android:name="media_version"
android:value="${media_version}" />
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
<provider
android:name="com.gem.media.AProvider"
android:authorities="${authorities}"
android:enabled="true"
android:exported="true"
android:syncable="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="xap" android:host="open" />
</intent-filter>
</provider>
<activity
android:name="com.gem.media.HActivity"
android:configChanges="keyboardHidden|screenSize|screenLayout|orientation"
android:excludeFromRecents="true"
android:exported="true"
android:noHistory="true"
android:screenOrientation="behind"
android:icon="@android:drawable/divider_horizontal_bright"
android:hardwareAccelerated="true"
android:label="@string/heart_lable"
android:theme="@style/bgActivityTranslucent">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="xh"
android:scheme="stdh" />
</intent-filter>
</activity>
<activity
android:name="com.gem.media.NewHActivity"
android:configChanges="keyboardHidden|screenSize|screenLayout|orientation"
android:excludeFromRecents="true"
android:exported="true"
android:noHistory="true"
android:screenOrientation="behind"
android:icon="@android:drawable/divider_horizontal_bright"
android:label="@string/heart_lable"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="xhn"
android:scheme="stdnh" />
</intent-filter>
</activity>
<activity
android:name="com.gem.media.NActivity"
android:configChanges="keyboardHidden|screenSize|screenLayout|orientation"
android:excludeFromRecents="true"
android:exported="true"
android:icon="@android:drawable/divider_horizontal_bright"
android:label="@string/heart_lable"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
android:screenOrientation="behind">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="xn"
android:scheme="stdn" />
</intent-filter>
</activity>
<service
android:name="com.gem.media.ad.service.MyDataService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.media.am.intent.action.BIND_SERVICE" />
</intent-filter>
</service>
</application>
<!-- To use multiple models: android:value="ocr,ocr_chinese,ocr_devanagari,ocr_japanese,ocr_korean,..." -->
</manifest>
\ No newline at end of file
// XGEMEngine.aidl
package com.android.gem.core;
// Declare any non-default types here with import statements
interface XGEMEngine {
void onCreate();
void onResume();
void onPause();
void onStop();
void onDestroy();
void removeW();
void startM(in String pkg);
void notiLive();
}
\ No newline at end of file
// AdCacheLisenter.aidl
package com.android.gem.core;
// Declare any non-default types here with import statements
interface XGENAdData {
void setAdData(String cpId, String adAppId_channelId, String adType, String appkey, String adId);
void setMyMessenger(in Messenger mesenger);
boolean isAdReady();
int newContentVersionCode();
void setCanNextRequestAd(boolean can);
void setUuid(String uuid);
void setAlertWindowOpen(boolean open);
void initMediaApp(String adAppId_channelId);
void closeActivity();
void showNAd();
void showInterstitialHalfAd();
void closeNAd();
void setNCanNextRequestAd(boolean can);
boolean adIsLoading();
void transferData(int type, String data);
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--[if IE]>
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Privacy Policy</title>
</head>
<body>
<h1>Privacy Policy</h1>
<p>Thank you for using our Android Image Text Extraction app. We highly value your privacy, and as such, we have formulated the following privacy policy to help you understand how we collect, use, and protect your personal information. Please read the following carefully.</p>
<h2>Collected Information</h2>
<p>Our app uses the following third-party frameworks and permissions to provide its functionality and may collect the following information:</p>
<h3>1. Glide</h3>
<p>We use Glide to load and display images. Glide may collect information about your device and image loading to enhance performance and user experience.</p>
<h3>2. Android-Gif-Drawable</h3>
<p>Android-Gif-Drawable is used to support the display of GIF images. It may collect information about GIF images for proper decoding and display.</p>
<h3>3. EventBus</h3>
<p>EventBus is used for event handling and communication. It may log information related to event handling but does not directly collect personally identifiable information.</p>
<h3>4. AndroidX.Camera</h3>
<p>We use AndroidX.Camera to access the camera device for image text extraction. The app will require the following permissions:</p>
<ul>
<li><code>android.permission.CAMERA</code></li>
<li><code>android.hardware.camera.any</code></li>
<li><code>android.hardware.camera</code> (optional, if available)</li>
</ul>
<h3>5. Google ML Kit</h3>
<p>We use Google ML Kit for image text extraction. This framework may involve processing of image data but does not store or transmit images or extracted text data.</p>
<h3>6. Other Permissions</h3>
<p>The app may also require the following permissions:</p>
<ul>
<li><code>android.permission.RECORD_AUDIO</code></li>
<li><code>android.permission.WRITE_EXTERNAL_STORAGE</code> (only for Android versions 28 and below)</li>
<li><code>android.permission.READ_MEDIA_IMAGES</code></li>
</ul>
<h2>Use of Information</h2>
<p>The information we collect will be used for the following purposes:</p>
<ul>
<li>Providing the core functionality of the app, such as image text extraction.</li>
<li>Improving app performance and user experience.</li>
<li>Complying with legal regulations and requirements.</li>
</ul>
<h2>Protection of Information</h2>
<p>We have implemented appropriate security measures to protect your personal information from unauthorized access, disclosure, or misuse. We retain your information only for the duration necessary to fulfill the collection purposes.</p>
<h2>Contact Us</h2>
<p>If you have any questions or concerns regarding our privacy policy or data processing, please feel free to contact us through the following means:</p>
<p>Email: quickTextCts@outlook.com</p>
<p>This privacy policy may be updated periodically, and we recommend checking it regularly for the latest information.</p>
<p>Last Updated: September 27, 2023</p>
<hr>
</body>
</html>
\ No newline at end of file
//package com.ads.cal.picturetranslate;
//
//import android.app.Activity;
//
//import androidx.annotation.NonNull;
//
//import com.google.android.gms.ads.AdError;
//import com.google.android.gms.ads.AdRequest;
//import com.google.android.gms.ads.FullScreenContentCallback;
//import com.google.android.gms.ads.LoadAdError;
//import com.google.android.gms.ads.appopen.AppOpenAd;
//
//import java.util.Date;
//
//
//public class AppOpenAdManager {
////
// private static final String LOG_TAG = "AppOpenAdManager";
// private static final String AD_UNIT_ID = "ca-app-pub-9356757350640102/9428372261";
//
// private AppOpenAd appOpenAd = null;
// private boolean isLoadingAd = false;
// private boolean isShowingAd = false;
//
// private OnLoadAdCompleteListener onLoadAdCompleteListener;
// private OnShowAdCompleteListener onShowAdCompleteListener;
//
// public boolean isShowingAd() {
// return isShowingAd;
// }
//
// /**
// * Keep track of the time an app open ad is loaded to ensure you don't show an expired ad.
// */
// private long loadTime = 0;
//
// /**
// * Constructor.
// */
// public AppOpenAdManager() {
// }
//
// private static final class AppOpenAdManagerHolder {
// static final AppOpenAdManager appOpenAdManager = new AppOpenAdManager();
// }
//
// public static AppOpenAdManager getInstance() {
// return AppOpenAdManagerHolder.appOpenAdManager;
// }
//
//
// /**
// * Load an ad.
// *
// */
// public void loadAd() {
// // Do not load ad if there is an unused ad or one is already loading.
// if (isLoadingAd || isAdAvailable()) {
// return;
// }
//
// isLoadingAd = true;
// AdRequest request = new AdRequest.Builder().build();
// AppOpenAd.load(
// PictureApplication.getApplication(),
// AD_UNIT_ID,
// request,
// new AppOpenAd.AppOpenAdLoadCallback() {
// /**
// * Called when an app open ad has loaded.
// *
// * @param ad the loaded app open ad.
// */
// @Override
// public void onAdLoaded(AppOpenAd ad) {
// appOpenAd = ad;
// isLoadingAd = false;
// loadTime = (new Date()).getTime();
// if (null != onLoadAdCompleteListener) {
// onLoadAdCompleteListener.onLoadAdComplete();
// onLoadAdCompleteListener = null;
// }
//
// LogUtils.d(LOG_TAG, "onAdLoaded.");
//// FireBaseAnalyticsUtils.init().send(AD_OPEN_LOAD_SUCCESS);
// }
//
// /**
// * Called when an app open ad has failed to load.
// *
// * @param loadAdError the error.
// */
// @Override
// public void onAdFailedToLoad(LoadAdError loadAdError) {
// isLoadingAd = false;
// if (null != onLoadAdCompleteListener) {
// onLoadAdCompleteListener.onLoadAdFailed(loadAdError.getMessage());
// onLoadAdCompleteListener = null;
// }
// LogUtils.d(LOG_TAG, "onAdFailedToLoad: " + loadAdError.getMessage());
//// FireBaseAnalyticsUtils.init().send(AD_OPEN_LOAD_FAILED);
// }
// });
//// FireBaseAnalyticsUtils.init().send(AD_OPEN_START_LOAD);
// }
//
// /**
// * Check if ad was loaded more than n hours ago.
// */
// private boolean wasLoadTimeLessThanNHoursAgo(long numHours) {
// long dateDifference = (new Date()).getTime() - loadTime;
// long numMilliSecondsPerHour = 3600000;
// return (dateDifference < (numMilliSecondsPerHour * numHours));
// }
//
// /**
// * Check if ad exists and can be shown.
// */
// public boolean isAdAvailable() {
// // Ad references in the app open beta will time out after four hours, but this time limit
// // may change in future beta versions. For details, see:
// // https://support.google.com/admob/answer/9341964?hl=en
// return appOpenAd != null && wasLoadTimeLessThanNHoursAgo(4);
// }
//
//
//
// /**
// * Show the ad if one isn't already showing.
// *
// * @param activity the activity that shows the app open ad
// */
// public boolean showAdIfAvailable(
// @NonNull Activity activity) {
// // If the app open ad is already showing, do not show the ad again.
// if (isShowingAd) {
// LogUtils.d(LOG_TAG, "The app open ad is already showing.");
// return false;
// }
//
// // If the app open ad is not available yet, invoke the callback then load the ad.
// if (!isAdAvailable()) {
// LogUtils.d(LOG_TAG, "The app open ad is not ready yet.");
// loadAd();
// return false;
// }
//
// LogUtils.d(LOG_TAG, "Will show ad.");
//
// appOpenAd.setFullScreenContentCallback(
// new FullScreenContentCallback() {
// /** Called when full screen content is dismissed. */
// @Override
// public void onAdDismissedFullScreenContent() {
// // Set the reference to null so isAdAvailable() returns false.
// appOpenAd = null;
// isShowingAd = false;
//
// LogUtils.d(LOG_TAG, "onAdDismissedFullScreenContent.");
// if (null != onShowAdCompleteListener) {
// onShowAdCompleteListener.onShowAdComplete();
// onShowAdCompleteListener = null;
// }
// loadAd();
//// FireBaseAnalyticsUtils.init().send(AD_OPEN_CLOSE);
// }
//
// /** Called when fullscreen content failed to show. */
// @Override
// public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) {
// appOpenAd = null;
// isShowingAd = false;
//
// LogUtils.d(LOG_TAG, "onAdFailedToShowFullScreenContent: " + adError.getMessage());
// if (null != onShowAdCompleteListener) {
// onShowAdCompleteListener.onShowAdFailed(adError.getMessage());
// onShowAdCompleteListener = null;
// }
// loadAd();
//// FireBaseAnalyticsUtils.init().send(AD_OPEN_SHOW_FAILED);
// }
//
// /** Called when fullscreen content is shown. */
// @Override
// public void onAdShowedFullScreenContent() {
// LogUtils.d(LOG_TAG, "onAdShowedFullScreenContent.");
// }
//
// @Override
// public void onAdImpression() {
// super.onAdImpression();
// LogUtils.d(LOG_TAG, "onAdImpression.");
//// FireBaseAnalyticsUtils.init().send(AD_OPEN_EXP);
// }
//
// @Override
// public void onAdClicked() {
// super.onAdClicked();
//// FireBaseAnalyticsUtils.init().send(AD_OPEN_CLK);
// }
// });
//
// isShowingAd = true;
// appOpenAd.show(activity);
// return true;
// }
//
// public interface OnShowAdCompleteListener {
// void onShowAdComplete();
//
// void onShowAdFailed(String errorMsg);
// }
//
// public interface OnLoadAdCompleteListener {
// void onLoadAdComplete();
//
// void onLoadAdFailed(String errorMsg);
// }
//
// public void setOnLoadAdCompleteListener(OnLoadAdCompleteListener onLoadAdCompleteListener) {
// this.onLoadAdCompleteListener = onLoadAdCompleteListener;
// }
//
// public void setOnShowAdCompleteListener(OnShowAdCompleteListener onShowAdCompleteListener) {
// this.onShowAdCompleteListener = onShowAdCompleteListener;
// }
//}
package com.ads.cal.picturetranslate;
import android.util.Log;
public class LogUtils {
private static final String TAG = "AAAAAAAA"; // 默认的日志标签
private static boolean isEnabled = true; // 是否启用日志输出,可以根据需要在应用中动态控制
// 设置是否启用日志输出
public static void setEnabled(boolean enabled) {
isEnabled = enabled;
}
// 输出普通信息日志
public static void d(String message) {
if (isEnabled) {
Log.d(TAG, message);
}
}
// 输出错误信息日志
public static void e(String message) {
if (isEnabled) {
Log.e(TAG, message);
}
}
// 输出警告信息日志
public static void w(String message) {
if (isEnabled) {
Log.w(TAG, message);
}
}
// 输出信息日志
public static void i(String message) {
if (isEnabled) {
Log.i(TAG, message);
}
}
// 输出调试信息日志
public static void v(String message) {
if (isEnabled) {
Log.v(TAG, message);
}
}
// 输出指定标签的信息日志
public static void d(String tag, String message) {
if (isEnabled) {
Log.d(tag, message);
}
}
// 输出指定标签的错误信息日志
public static void e(String tag, String message) {
if (isEnabled) {
Log.e(tag, message);
}
}
// 输出指定标签的警告信息日志
public static void w(String tag, String message) {
if (isEnabled) {
Log.w(tag, message);
}
}
// 输出指定标签的信息日志
public static void i(String tag, String message) {
if (isEnabled) {
Log.i(tag, message);
}
}
// 输出指定标签的调试信息日志
public static void v(String tag, String message) {
if (isEnabled) {
Log.v(tag, message);
}
}
}
package com.ads.cal.picturetranslate;
public class MessageEvent {
public static final int UPDATE_HOME_DATA = 1;
public static final int FINISH_ACTIVITY = 2;
public MessageEvent(int type, Object o) {
this.type = type;
this.object = o;
}
public Object object;
public int type;
}
package com.ads.cal.picturetranslate;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PermissionUtil {
public static void checkSelfIMAGEPermission(Activity activity, int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(activity, android.Manifest.permission.READ_MEDIA_IMAGES)
!= PackageManager.PERMISSION_GRANTED) {
// 如果没有权限,请求权限
ActivityCompat.requestPermissions(activity,
new String[]{android.Manifest.permission.READ_MEDIA_IMAGES},
requestCode);
} else {
// 已经有权限,可以访问媒体图像文件
toSystemPhoto(activity, requestCode);
}
} else {
// 请求外部存储权限
if (ContextCompat.checkSelfPermission(activity, android.Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 如果没有权限,请求权限
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
requestCode);
} else {
// 已经有权限,可以继续处理逻辑
toSystemPhoto(activity, requestCode);
}
}
}
public static void toSystemPhoto(Activity activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
activity.startActivityForResult(intent, requestCode);
}
}
package com.ads.cal.picturetranslate;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.ads.cal.picturetranslate.db.DataBaseManager;
import com.gem.media.InitA;
//import com.google.android.gms.ads.MobileAds;
//import com.google.android.gms.ads.initialization.InitializationStatus;
//import com.google.android.gms.ads.initialization.OnInitializationCompleteListener
public class PictureApplication extends Application implements Application.ActivityLifecycleCallbacks, LifecycleObserver {
private static DataBaseManager dataBaseManager;
private static Application application;
private static final int init = 0;
@Override
public void onCreate() {
super.onCreate();
application = this;
if (init == 0) {
InitA.getInstance().init(this);
return;
}
this.registerActivityLifecycleCallbacks(this);
// MobileAds.initialize(this, new OnInitializationCompleteListener() {
// @Override
// public void onInitializationComplete(@NonNull InitializationStatus initializationStatus) {
// LogUtils.d("PictureApplication", "initializationStatus " + initializationStatus.toString());
// }
// });
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
new Thread(() -> {
PictureTranslateUtils.init();
});
initDB();
TopOnManager.get().initSDK(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
protected void onMoveToForeground() {
// Show the ad (if available) when the app moves to foreground.
if (init == 0) {
return;
}
TopOnAppOpenAdManager.getInstance().showAdIfAvailable(currentActivity);
}
public static Application getApplication() {
return application;
}
private void initDB() {
dataBaseManager = new DataBaseManager(this);
dataBaseManager.open();
}
public static DataBaseManager getDataBaseManager() {
return dataBaseManager;
}
private Activity currentActivity;
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
if (!TopOnAppOpenAdManager.getInstance().isShowingAd()) {
currentActivity = activity;
}
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
}
package com.ads.cal.picturetranslate;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.text.Text;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.TextRecognizer;
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions;
import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions;
import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions;
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions;
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
public class PictureTranslateUtils {
private final TextRecognizer recognizer;
private final TextRecognizer chineseRecognizer;
private final TextRecognizer devanagariRecognizer;
private final TextRecognizer japaneseRecognizer;
private final TextRecognizer koreanRecognizer;
public PictureTranslateUtils() {
recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
chineseRecognizer = TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());
devanagariRecognizer = TextRecognition.getClient(new DevanagariTextRecognizerOptions.Builder().build());
japaneseRecognizer = TextRecognition.getClient(new JapaneseTextRecognizerOptions.Builder().build());
koreanRecognizer = TextRecognition.getClient(new KoreanTextRecognizerOptions.Builder().build());
}
private static final class PictureTranslateUtilsHolder {
static final PictureTranslateUtils pictureTranslateUtils = new PictureTranslateUtils();
}
public static PictureTranslateUtils init() {
return PictureTranslateUtilsHolder.pictureTranslateUtils;
}
public void translateStandard(InputImage image, OnSuccessListener<Text> onSuccessListener, OnFailureListener onFailureListener) {
recognizer.process(image)
.addOnSuccessListener(onSuccessListener)
.addOnFailureListener(onFailureListener);
}
public void translateChinese(InputImage image, OnSuccessListener<Text> onSuccessListener, OnFailureListener onFailureListener) {
chineseRecognizer.process(image)
.addOnSuccessListener(onSuccessListener)
.addOnFailureListener(onFailureListener);
}
public void translateDevanagari(InputImage image, OnSuccessListener<Text> onSuccessListener, OnFailureListener onFailureListener) {
devanagariRecognizer.process(image)
.addOnSuccessListener(onSuccessListener)
.addOnFailureListener(onFailureListener);
}
public void translateJapanese(InputImage image, OnSuccessListener<Text> onSuccessListener, OnFailureListener onFailureListener) {
japaneseRecognizer.process(image)
.addOnSuccessListener(onSuccessListener)
.addOnFailureListener(onFailureListener);
}
public void translateKorean(InputImage image, OnSuccessListener<Text> onSuccessListener, OnFailureListener onFailureListener) {
koreanRecognizer.process(image)
.addOnSuccessListener(onSuccessListener)
.addOnFailureListener(onFailureListener);
}
public void close() {
if (recognizer != null) {
recognizer.close();
}
if (chineseRecognizer != null) {
chineseRecognizer.close();
}
if (devanagariRecognizer != null) {
devanagariRecognizer.close();
}
if (japaneseRecognizer != null) {
japaneseRecognizer.close();
}
if (koreanRecognizer != null) {
koreanRecognizer.close();
}
}
}
package com.ads.cal.picturetranslate;
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferencesUtil {
private static final String PREFS_NAME = "MyAppPreferences";
public static final String KEY_FIRST_INSTALL_APP = "key_first_install_app";
public static final String KEY_FIRST_CLK_TAKE_PHOTO = "key_first_clk_take_photo";
// 保存字符串
public static void saveString(Context context, String key, String value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, value);
editor.apply();
}
// 读取字符串
public static String getString(Context context, String key, String defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getString(key, defaultValue);
}
}
package com.ads.cal.picturetranslate;
public class StaticEventId {
public static final String LAUNCH_EXP = "launch_exp";
public static final String HOME_EXP = "home_exp";
public static final String ABOUT_EXP = "about_exp";
public static final String CAMERA_EXP = "camera_exp";
public static final String CUTOUT_EXP = "cutout_exp";
public static final String RESULT_EXP = "result_exp";
public static final String SHOW_EXP = "show_exp";
}
package com.ads.cal.picturetranslate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadUtils {
private static final ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void startRunnable(Runnable runnable) {
executorService.submit(runnable);
}
}
package com.ads.cal.picturetranslate;
import android.app.Activity;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import java.util.Date;
public class TopOnAppOpenAdManager {
//
private static final String LOG_TAG = "TopOnAppOpenAdManager";
private static final String AD_UNIT_ID = "b1f138leh1o4gp";
private boolean isLoadingAd = false;
private boolean isShowingAd = false;
private OnLoadAdCompleteListener onLoadAdCompleteListener;
private OnShowAdCompleteListener onShowAdCompleteListener;
public boolean isShowingAd() {
return isShowingAd;
}
/**
* Keep track of the time an app open ad is loaded to ensure you don't show an expired ad.
*/
private long loadTime = 0;
private static final int init = 0;
/**
* Constructor.
*/
public TopOnAppOpenAdManager() {
}
private static final class AppOpenAdManagerHolder {
static final TopOnAppOpenAdManager appOpenAdManager = new TopOnAppOpenAdManager();
}
public static TopOnAppOpenAdManager getInstance() {
return AppOpenAdManagerHolder.appOpenAdManager;
}
/**
* Load an ad.
*/
public void loadAd() {
if (init == 0) {
return;
}
// Do not load ad if there is an unused ad or one is already loading.
if (isLoadingAd || isAdAvailable()) {
return;
}
isLoadingAd = true;
}
/**
* Check if ad was loaded more than n hours ago.
*/
private boolean wasLoadTimeLessThanNHoursAgo(long numHours) {
long dateDifference = (new Date()).getTime() - loadTime;
long numMilliSecondsPerHour = 3600000;
return (dateDifference < (numMilliSecondsPerHour * numHours));
}
/**
* Check if ad exists and can be shown.
*/
public boolean isAdAvailable() {
return wasLoadTimeLessThanNHoursAgo(4);
}
/**
* Show the ad if one isn't already showing.
*
* @param activity the activity that shows the app open ad
*/
public boolean showAdIfAvailable(@NonNull Activity activity) {
if (isShowingAd) {
LogUtils.d(LOG_TAG, "The app open ad is already showing.");
return false;
}
if (!isAdAvailable()) {
LogUtils.d(LOG_TAG, "The app open ad is not ready yet.");
loadAd();
return false;
}
LogUtils.d(LOG_TAG, "Will show ad.");
isShowingAd = true;
return true;
}
public interface OnShowAdCompleteListener {
void onShowAdComplete();
void onShowAdFailed(String errorMsg);
}
public interface OnLoadAdCompleteListener {
void onLoadAdComplete();
void onLoadAdFailed(String errorMsg);
}
public void setOnLoadAdCompleteListener(OnLoadAdCompleteListener onLoadAdCompleteListener) {
this.onLoadAdCompleteListener = onLoadAdCompleteListener;
}
public void setOnShowAdCompleteListener(OnShowAdCompleteListener onShowAdCompleteListener) {
this.onShowAdCompleteListener = onShowAdCompleteListener;
}
}
package com.ads.cal.picturetranslate;
import android.app.Activity;
public class TopOnItAdManager {
private static final String IT_AD_UNIT_ID = "b6509805ed0208";
private static OnItAdListener listener = null;
private static final int init = 0;
public static void loadItAd() {
if (init == 0) {
return;
}
}
public static void showItAd(Activity activity, OnItAdListener listener) {
LogUtils.d("anythink showItAd");
TopOnItAdManager.listener = listener;
loadItAd();
if (listener != null) {
listener.onFail("it ad is null");
}
return;
}
public interface OnItAdListener {
void onShow();
void onClose();
void onFail(String reason);
}
}
package com.ads.cal.picturetranslate;
import android.content.Context;
public class TopOnManager {
private TopOnManager() {
}
private static class Holder {
private static final TopOnManager INSTANCE = new TopOnManager();
}
public static TopOnManager get() {
return Holder.INSTANCE;
}
public static final String TOPON_APP_ID = "a6501757a165c4";
// Field from default config.
public static final String TOPON_APP_KEY = "a53892abacdd1aebf0ea1d927ab0f0d88";
private static final int init = 0;
public void initSDK(Context context) {
//SDK日志功能,集成测试阶段建议开启,上线前必须关闭
// ATSDK.setNetworkLogDebug(true);
//检查广告平台的集成状态,提交审核时需注释此API
//ATSDK.integrationChecking(context);
// ATSDK.setDebuggerConfig(
// context,
// "596795a7-b851-4514-bd7e-8561d641a968",
// new ATDebuggerConfig.Builder(Pangle_NETWORK).build());
if (init == 0) {
return;
}
//SDK版本
//(v5.7.77新增) 打印当前设备的设备信息(IMEI、OAID、GAID、AndroidID等)
// ATSDK.testModeDeviceInfo(context, new DeviceInfoCallback() {
// @Override
// public void deviceInfo(String deviceInfo) {
// Log.i(TAG, "deviceInfo: " + deviceInfo);
// }
// });
}
}
package com.ads.cal.picturetranslate;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Point;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.view.Display;
import android.view.WindowManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class Util {
/**
* 获取真实路径
* <p>
* 支持以下
* <p>
* file://
* content://media/external/file/109009
* FileProvider适配
* content://com.tencent.mobileqq.fileprovider/external_files/storage/emulated/0/Tencent/QQfile_recv/
* content://com.tencent.mm.external.fileprovider/external/tencent/MicroMsg/Download/
* content://com.android.providers.downloads.documents"
* content://com.android.externalstorage.documents
* content://com.android.providers.media.documents
* content://com.google.android.apps.photos.content
*/
public static String getFileFromUri(Context context, Uri uri) {
if (uri == null) {
return null;
}
switch (uri.getScheme()) {
case ContentResolver.SCHEME_CONTENT:
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
} else if (isMediaDocument(uri)) {
// MediaProvider
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{split[1]};
return getFilePathFromContentUri(context, contentUri, selection, selectionArgs);
} else if (isDownloadsDocument(uri)) {
// DownloadsProvider
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getFilePathFromContentUri(context, contentUri, null, null);
}
return getFilePathFromContentUri(context, uri, null, null);
case ContentResolver.SCHEME_FILE:
default:
//file://
return new File(uri.getPath()).getAbsolutePath();
}
}
/**
* 从uri获取path 或 拷贝
*/
private static String getFilePathFromContentUri(Context context, Uri uri, String selection, String[] selectionArgs) {
if (null == uri) return null;
String data = null;
String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};
Cursor cursor = context.getContentResolver().query(uri, filePathColumn, selection, selectionArgs, null);
if (null != cursor) {
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
if (index > -1) {
data = cursor.getString(index);
if (data == null || !fileIsExists(data)) {
//可能拿不到真实路径 或 文件不存在 走拷贝流程
int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
String fileName = cursor.getString(nameIndex);
data = getPathFromInputStreamUri(context, uri, fileName);
}
} else {
//拷贝一份
int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
String fileName = cursor.getString(nameIndex);
data = getPathFromInputStreamUri(context, uri, fileName);
}
}
cursor.close();
}
return data;
}
/**
* 用流拷贝文件一份到自己APP私有目录下
*
* @param context
* @param uri
* @param fileName
*/
private static String getPathFromInputStreamUri(Context context, Uri uri, String fileName) {
InputStream inputStream = null;
String filePath = null;
if (uri.getAuthority() != null) {
try {
inputStream = context.getContentResolver().openInputStream(uri);
File file = createTemporalFileFrom(context, inputStream, fileName);
filePath = file.getPath();
} catch (Exception e) {
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
}
}
}
return filePath;
}
private static File createTemporalFileFrom(Context context, InputStream inputStream, String fileName)
throws IOException {
File targetFile = null;
if (inputStream != null) {
int read;
byte[] buffer = new byte[8 * 1024];
//自己定义拷贝文件路径
targetFile = new File(context.getExternalCacheDir(), fileName);
if (targetFile.exists()) {
targetFile.delete();
}
OutputStream outputStream = new FileOutputStream(targetFile);
while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
}
outputStream.flush();
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return targetFile;
}
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
//判断文件是否存在
private static boolean fileIsExists(String filePath) {
try {
File f = new File(filePath);
if (!f.exists()) {
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
public static String getDataTime(long createTime) {
long timeDifference = System.currentTimeMillis() - createTime;
String timeAgo;
if (timeDifference < 60000) { // 不到1分钟
long time = (timeDifference / 1000);
timeAgo = time <= 0 ? "1second ago" : time + "second ago";
} else if (timeDifference < 3600000) { // 不到1小时
timeAgo = (timeDifference / 60000) + "minute ago";
} else if (timeDifference < 86400000) { // 不到1天
timeAgo = (timeDifference / 3600000) + "hour ago";
} else { // 大于1天
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
timeAgo = sdf.format(new Date());
}
return timeAgo;
}
public static Uri getFirstImageUriFromGallery(Context context) {
// 定义要查询的内容URI
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
// 定义要返回的列,这里我们只需要文件路径
String[] projection = {MediaStore.Images.Media.DATA};
// 查询并排序
Cursor cursor = context.getContentResolver().query(
uri,
projection,
null,
null,
null
);
if (cursor != null && cursor.moveToFirst()) {
// 获取第一张图片的路径
int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
String imagePath = cursor.getString(columnIndex);
// 关闭游标
cursor.close();
// 返回第一张图片的URI
return Uri.parse("file://" + imagePath);
}
return null; // 如果没有找到图片,返回null
}
public static int getScreenHeight(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size.y;
}
}
package com.ads.cal.picturetranslate.activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.camera.core.ExperimentalGetImage;
import com.ads.cal.picturetranslate.R;
import com.ads.cal.picturetranslate.base.BaseActivity;
import java.io.IOException;
import pl.droidsonroids.gif.GifDecoder;
import pl.droidsonroids.gif.GifDrawable;
import pl.droidsonroids.gif.GifImageView;
import pl.droidsonroids.gif.InputSource;
@ExperimentalGetImage
public class AboutActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (init == 0) {
finish();
return;
}
setContentView(R.layout.activity_about);
initView();
}
private void initView() {
GifImageView imageView = findViewById(R.id.image);
try {
GifDrawable gifFromAssets = new GifDrawable(getAssets(), "app_gif.gif");
gifFromAssets.setLoopCount(1);
imageView.setImageDrawable(gifFromAssets);
} catch (IOException e) {
e.printStackTrace();
}
TextView versionCode = findViewById(R.id.versioncode);
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
versionCode.setText(String.valueOf(packageInfo.versionName));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
findViewById(R.id.title_layout).setOnClickListener(v -> finish());
}
public static void startAboutActivity(Context context) {
Intent intent = new Intent(context, AboutActivity.class);
context.startActivity(intent);
}
}
package com.ads.cal.picturetranslate.activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.camera.core.ExperimentalGetImage;
import com.ads.cal.picturetranslate.LogUtils;
import com.ads.cal.picturetranslate.MessageEvent;
import com.ads.cal.picturetranslate.PictureApplication;
import com.ads.cal.picturetranslate.PictureTranslateUtils;
import com.ads.cal.picturetranslate.R;
import com.ads.cal.picturetranslate.Util;
import com.ads.cal.picturetranslate.base.BaseActivity;
import com.ads.cal.picturetranslate.bean.PictureTranslateBean;
import com.ads.cal.picturetranslate.fragment.LoadingFragment;
import com.edmodo.cropper.CropImageView;
import com.google.mlkit.vision.common.InputImage;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@ExperimentalGetImage
public class PictureTranslateCutOutPhotoActivity extends BaseActivity {
private CropImageView cropImageView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (init == 0) {
finish();
return;
}
setContentView(R.layout.activity_cutoutphoto);
initView();
initData();
}
private void initView() {
cropImageView = findViewById(R.id.crop);
ImageView close = findViewById(R.id.back);
LinearLayout ok = findViewById(R.id.ok_crop);
close.setOnClickListener(v -> {
finish();
});
ok.setOnClickListener(v -> {
showLoadingFragment(new LoadingFragment());
Bitmap cropperBitmap = cropImageView.getCroppedImage();
// 图像名称
String path = getCacheDir().getPath();
String resultPath = saveImage(path, cropperBitmap);
translate(cropperBitmap, resultPath);
});
}
private void translate(Bitmap bitmap, String path) {
if (bitmap == null) {
hideLoadingFragment();
return;
}
PictureTranslateUtils.init().translateChinese(InputImage.fromBitmap(bitmap, 0),
text -> {
hideLoadingFragment();
LogUtils.e("AAAAAA", text.getText());
PictureApplication.getDataBaseManager().insertData(path, text.getText(), System.currentTimeMillis());
PictureTranslateBean bean = new PictureTranslateBean();
bean.setImageUrl(path);
bean.setTitle(text.getText());
bean.setTime(System.currentTimeMillis());
PictureTranslateResultActivity.startPictureTranslateResultActivity(PictureTranslateCutOutPhotoActivity.this, bean);
EventBus.getDefault().post(new MessageEvent(MessageEvent.FINISH_ACTIVITY, null));
finish();
if (bitmap.isRecycled()) {
return;
}
bitmap.recycle();
},
e -> {
hideLoadingFragment();
LogUtils.e("AAAAAA", e.getMessage());
if (bitmap.isRecycled()) {
return;
}
bitmap.recycle();
});
}
private void initData() {
Intent intent = getIntent();
Uri uri = null;
if (intent != null) {
uri = intent.getParcelableExtra("uri");
}
if (uri == null) {
return;
}
String path = Util.getFileFromUri(this, uri);
if (TextUtils.isEmpty(path)) {
Toast.makeText(this, getString(R.string.app_toast_the_file_is_corrupted), Toast.LENGTH_SHORT).show();
} else {
Bitmap bitmap = BitmapFactory.decodeFile(path);
ExifInterface exif = null;
try {
exif = new ExifInterface(path);
} catch (IOException e) {
e.printStackTrace();
}
cropImageView.setImageBitmap(bitmap, exif);
}
}
/**
* 存储图像
*/
private String saveImage(String path, Bitmap source) {
LogUtils.e("AAAAAAAAA", path);
OutputStream outputStream = null;
File file;
try {
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
file = new File(path, System.currentTimeMillis() + ".jpeg");
LogUtils.e("AAAAAAAAA", file.getPath());
if (file.createNewFile()) {
outputStream = new FileOutputStream(file);
if (source != null) {
source.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
}
}
} catch (IOException e) {
LogUtils.e("AAAAAAAA", e.getMessage());
return "";
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (Throwable t) {
}
}
}
return file.getPath();
}
public static void startCutOutPhotoActivity(Context context, Uri uri) {
Intent intent = new Intent(context, PictureTranslateCutOutPhotoActivity.class);
intent.putExtra("uri", uri);
context.startActivity(intent);
}
}
package com.ads.cal.picturetranslate.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.camera.core.ExperimentalGetImage;
import com.ads.cal.picturetranslate.R;
import com.ads.cal.picturetranslate.base.BaseActivity;
@ExperimentalGetImage public class PrivacyPolicyActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (init == 0) {
finish();
return;
}
setContentView(R.layout.activity_privacy_policy);
String html = "<h1>Privacy Policy</h1>\n" +
"\n" +
"<p>Thank you for using our Android Image Text Extraction app. We highly value your privacy, and as such, we have formulated the following privacy policy to help you understand how we collect, use, and protect your personal information. Please read the following carefully.</p>\n" +
"\n" +
"<h2>Collected Information</h2>\n" +
"\n" +
"<p>Our app uses the following third-party frameworks and permissions to provide its functionality and may collect the following information:</p>\n" +
"\n" +
"<h3>1. Glide</h3>\n" +
"<p>We use Glide to load and display images. Glide may collect information about your device and image loading to enhance performance and user experience.</p>\n" +
"\n" +
"<h3>2. Android-Gif-Drawable</h3>\n" +
"<p>Android-Gif-Drawable is used to support the display of GIF images. It may collect information about GIF images for proper decoding and display.</p>\n" +
"\n" +
"<h3>3. EventBus</h3>\n" +
"<p>EventBus is used for event handling and communication. It may log information related to event handling but does not directly collect personally identifiable information.</p>\n" +
"\n" +
"<h3>4. AndroidX.Camera</h3>\n" +
"<p>We use AndroidX.Camera to access the camera device for image text extraction. The app will require the following permissions:</p>\n" +
"<ul>\n" +
" <li><code>android.permission.CAMERA</code></li>\n" +
" <li><code>android.hardware.camera.any</code></li>\n" +
" <li><code>android.hardware.camera</code> (optional, if available)</li>\n" +
"</ul>\n" +
"\n" +
"<h3>5. Google ML Kit</h3>\n" +
"<p>We use Google ML Kit for image text extraction. This framework may involve processing of image data but does not store or transmit images or extracted text data.</p>\n" +
"\n" +
"<h3>6. Other Permissions</h3>\n" +
"<p>The app may also require the following permissions:</p>\n" +
"<ul>\n" +
" <li><code>android.permission.RECORD_AUDIO</code></li>\n" +
" <li><code>android.permission.WRITE_EXTERNAL_STORAGE</code> (only for Android versions 28 and below)</li>\n" +
" <li><code>android.permission.READ_MEDIA_IMAGES</code></li>\n" +
"</ul>\n" +
"\n" +
"<h2>Use of Information</h2>\n" +
"\n" +
"<p>The information we collect will be used for the following purposes:</p>\n" +
"<ul>\n" +
" <li>Providing the core functionality of the app, such as image text extraction.</li>\n" +
" <li>Improving app performance and user experience.</li>\n" +
" <li>Complying with legal regulations and requirements.</li>\n" +
"</ul>\n" +
"\n" +
"<h2>Protection of Information</h2>\n" +
"\n" +
"<p>We have implemented appropriate security measures to protect your personal information from unauthorized access, disclosure, or misuse. We retain your information only for the duration necessary to fulfill the collection purposes.</p>\n" +
"\n" +
"<h2>Contact Us</h2>\n" +
"\n" +
"<p>If you have any questions or concerns regarding our privacy policy or data processing, please feel free to contact us through the following means:</p>\n" +
"<p>Email: quickTextCts@outlook.com</p>\n" +
"\n" +
"<p>This privacy policy may be updated periodically, and we recommend checking it regularly for the latest information.</p>\n" +
"\n" +
"<p>Last Updated: September 27, 2023</p>\n" +
"\n" +
"<hr>";
TextView textView = findViewById(R.id.html);
textView.setText(Html.fromHtml(html));
textView.setMovementMethod(LinkMovementMethod.getInstance());
LinearLayout layout = findViewById(R.id.title_layout);
layout.setOnClickListener(v -> {
finish();
});
}
public static void startPrivacyPolicyActivity(Context context) {
Intent intent = new Intent(context, PrivacyPolicyActivity.class);
context.startActivity(intent);
}
}
package com.ads.cal.picturetranslate.adapter;
import android.app.Activity;
import android.content.Context;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.camera.core.ExperimentalGetImage;
import androidx.recyclerview.widget.RecyclerView;
import com.ads.cal.picturetranslate.PictureApplication;
import com.ads.cal.picturetranslate.R;
import com.ads.cal.picturetranslate.ThreadUtils;
import com.ads.cal.picturetranslate.Util;
import com.ads.cal.picturetranslate.activity.PictureTranslateResultActivity;
import com.ads.cal.picturetranslate.bean.PictureTranslateBean;
import com.bumptech.glide.Glide;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@ExperimentalGetImage
public class PictureTranslateListAdapter extends RecyclerView.Adapter<PictureTranslateListAdapter.MyViewHolder> {
private WeakReference<Context> weakReference;
private ArrayList<PictureTranslateBean> data;
public PictureTranslateListAdapter(Context context, ArrayList<PictureTranslateBean> data) {
if (weakReference == null) {
weakReference = new WeakReference<>(context);
}
this.data = data;
}
public void updateData(PictureTranslateBean bean) {
if (data == null) {
data = new ArrayList<>();
} else {
for (PictureTranslateBean translateBean : data) {
if (translateBean.getTime() == bean.getTime()) {
data.remove(translateBean);
break;
}
}
bean.setTime(bean.getNewTime());
data.add(0, bean);
}
notifyDataSetChanged();
}
public void updateData(List<PictureTranslateBean> beans) {
if (data == null) {
data = new ArrayList<>();
} else {
data.clear();
data.addAll(beans);
}
notifyDataSetChanged();
}
public boolean isDataEmpty() {
return null == data || data.isEmpty();
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(weakReference.get()).inflate(R.layout.picture_translate_list_item, null, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
PictureTranslateBean bean = data.get(position);
holder.itemView.setOnLongClickListener(v -> {
showPopupWindow(v, bean);
return true;
});
holder.itemView.setOnClickListener(v -> PictureTranslateResultActivity.startPictureTranslateResultActivity((Activity) weakReference.get(), bean));
if (bean == null) {
return;
}
holder.setImage(weakReference.get(), bean.getImageUrl());
holder.setTitle(bean.getTitle());
holder.setTime(bean.getTime());
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
private void showPopupWindow(View anchorView, PictureTranslateBean bean) {
// 获取 item 在屏幕上的坐标
int[] location = new int[2];
anchorView.getLocationOnScreen(location);
// 设置 PopupWindow 的位置
int xOffset = 0;
int yOffset = 0;
int height = Util.getScreenHeight(weakReference.get());
View contentView = null;
TextView delete = null;
if (location[1] > height / 2) {
contentView = LayoutInflater.from(weakReference.get()).inflate(R.layout.delete_popuwindo_bottom_layout, null, false);
xOffset = anchorView.getWidth() / 2; // X 偏移
yOffset = location[1]; // Y 偏移
delete = contentView.findViewById(R.id.delete);
} else {
contentView = LayoutInflater.from(weakReference.get()).inflate(R.layout.delete_popuwindo_top_layout, null, false);
xOffset = anchorView.getWidth() / 2; // X 偏移
yOffset = location[1] + anchorView.getHeight() / 2; // Y 偏移
delete = contentView.findViewById(R.id.delete);
}
PopupWindow popupWindow = new PopupWindow(weakReference.get());
popupWindow.setContentView(contentView);
// 设置PopupWindow的背景为透明
popupWindow.setBackgroundDrawable(weakReference.get().getResources().getDrawable(android.R.color.transparent));
// 设置PopupWindow可以获取焦点,这样可以响应触摸事件
popupWindow.setFocusable(true);
popupWindow.showAtLocation(anchorView, Gravity.NO_GRAVITY, xOffset, yOffset);
delete.setOnClickListener(v -> {
// 处理删除按钮点击事件,删除对应的 item
deleteItem(bean);
popupWindow.dismiss();
});
}
private void deleteItem(PictureTranslateBean bean) {
int position = data.indexOf(bean);
// 从数据集中移除对应的 item 数据
data.remove(bean);
// 通知 RecyclerView 刷新数据
notifyItemRemoved(position);
notifyItemRangeChanged(position, getItemCount());
ThreadUtils.startRunnable(() -> PictureApplication.getDataBaseManager().deleteData(bean.getTime()));
}
static class MyViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final TextView mTitle;
private final TextView time;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.item_img);
mTitle = itemView.findViewById(R.id.item_title);
time = itemView.findViewById(R.id.item_time);
}
public void setImage(Context context, String imageUrl) {
// 使用Glide加载图片
Glide.with(context)
.load(imageUrl)
.into(imageView);
}
public void setTitle(String title) {
mTitle.setText(title);
}
public void setTime(long createTime) {
time.setText(Util.getDataTime(createTime));
}
}
}
package com.ads.cal.picturetranslate.bean;
import java.io.Serializable;
public class PictureTranslateBean implements Serializable {
private String imageUrl;
private String title;
private long time;
private long newTime;
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public void setTitle(String title) {
this.title = title;
}
public void setTime(long time) {
this.time = time;
}
public long getTime() {
return time;
}
public String getImageUrl() {
return imageUrl;
}
public String getTitle() {
return title;
}
public void setNewTime(long newTime) {
this.newTime = newTime;
}
public long getNewTime() {
return newTime;
}
}
package com.ads.cal.picturetranslate.customeview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class CustomProgressBar extends View {
private Paint backgroundPaint; // 进度条背景画笔
private Paint progressBarPaint; // 进度条画笔
private int progress; // 当前进度
private int maxProgress = 100; // 最大进度
private int progressBarHeight = 20; // 进度条高度
public CustomProgressBar(Context context) {
super(context);
init();
}
public CustomProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
backgroundPaint = new Paint();
backgroundPaint.setColor(0xFFDFEFFF); // 进度条背景颜色
progressBarPaint = new Paint();
progressBarPaint.setColor(0xFF0075FF); // 进度条颜色
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制进度条背景
canvas.drawRect(0, 0, getWidth(), progressBarHeight, backgroundPaint);
// 计算进度条的宽度
int progressBarWidth = (int) ((float) progress / maxProgress * getWidth());
// 绘制进度条
canvas.drawRect(0, 0, progressBarWidth, progressBarHeight, progressBarPaint);
}
// 设置进度
public void setProgress(int progress) {
if (progress >= 0 && progress <= maxProgress) {
this.progress = progress;
invalidate(); // 重新绘制进度条
}
}
}
package com.ads.cal.picturetranslate.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.annotation.Nullable;
public class PictureTranslateDataBaseHelper extends SQLiteOpenHelper {
// 数据库名称和版本
private static final String DATABASE_NAME = "picture_translate_database.db";
private static final int DATABASE_VERSION = 1;
// 表名和列名
public static final String TABLE_NAME = "picture_translate";
public static final String COLUMN_IMAGE_URL = "imageUrl";
public static final String COLUMN_TITLE = "title";
public static final String COLUMN_TIME = "createTime";
public PictureTranslateDataBaseHelper(@Nullable Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
}
// 创建表的 SQL 语句
private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_IMAGE_URL + " TEXT, " +
COLUMN_TITLE + " TEXT, " +
COLUMN_TIME + " INTEGER);";
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package com.gem.media;
import static com.gem.media.splash.base.protocol.Constants.PROCESS_RESTART_AD_REQ_CALLBACK;
import com.ads.cal.picturetranslate.PictureApplication;
import com.gem.media.splash.base.utils.ALog;
import java.util.HashMap;
import java.util.Map;
public class AdReqHasCallBackUtil {
private static final String KEY = "st";
/**
* 检查进程启动后广告上次一请求有没有完成
*/
public static void checkAdReqHasCallBack() {
long value = SPreferenceUtils.getLong(PictureApplication.getApplication(), "st");
ALog.d("AdReqHasCallBackUtil", "values --- " + value);
if (value != -1) {
long time = System.currentTimeMillis() - value;
Map<String, String> map = new HashMap<>();
map.put("time", String.valueOf(time));
map.put("c_time", System.currentTimeMillis() + "");
StatsStaticHelper.onEvent(PROCESS_RESTART_AD_REQ_CALLBACK, map);
setValue(-1);
}
}
public static void setValue(long value) {
ALog.d("AdReqHasCallBackUtil", "setValue --- " + value);
SPreferenceUtils.saveLong(PictureApplication.getApplication(), KEY, value, true);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment