├── .gitattributes ├── .gitignore ├── .idea ├── .name ├── codeStyles │ └── Project.xml ├── dictionaries │ └── havec.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── WordsAway_v0.0.4.3.apk │ └── output.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── krytro │ │ └── wordsaway │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── krytro │ │ │ └── wordsaway │ │ │ ├── AboutActivity.java │ │ │ ├── MainActivity.java │ │ │ └── WordsAway.java │ └── res │ │ ├── color │ │ └── btn_background_tint.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── button_background.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_about.xml │ │ └── activity_main.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── krytro │ └── wordsaway │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── html ├── about_content.html └── usage_content.html └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | WordsAway -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/dictionaries/havec.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | coolapk 5 | mhandler 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordsAway_Android 2 | 3 | 这是[WordsAway](https://github.com/NitroRCr/Words-away)的Android App版本 4 | 5 | 目前还处于勉强能用的阶段 6 | 7 | 用途介绍、使用说明等,请参考[网页版](https://wordsaway.texice.xyz) 8 | 9 | 下载请到[Releases](https://github.com/NitroRCr/WordsAway_Android/releases)页面 10 | 11 | 其实网页版完全够用,这不过是作者的Android开发入门练手项目(My first app.) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.krytro.wordsaway" 9 | minSdkVersion 22 10 | targetSdkVersion 29 11 | versionCode 6 12 | versionName "0.0.4.3" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled true 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: "libs", include: ["*.jar"]) 31 | implementation 'androidx.appcompat:appcompat:1.1.0' 32 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 35 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 36 | 37 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/release/WordsAway_v0.0.4.3.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NitroRCr/WordsAway_Android/bf15c28e4d2c3e39389b850b002c15ca00393a51/app/release/WordsAway_v0.0.4.3.apk -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.krytro.wordsaway", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "properties": [], 14 | "versionCode": 6, 15 | "versionName": "6", 16 | "enabled": true, 17 | "outputFile": "app-release.apk" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/krytro/wordsaway/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.krytro.wordsaway; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.krytro.wordsaway", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/krytro/wordsaway/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.krytro.wordsaway; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | import android.text.Html; 7 | import android.text.method.LinkMovementMethod; 8 | import android.widget.TextView; 9 | 10 | public class AboutActivity extends AppCompatActivity { 11 | TextView tv_content; 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_about); 17 | 18 | tv_content = findViewById(R.id.textView_about_content); 19 | tv_content.setText(Html.fromHtml(getString(R.string.about_content))); 20 | tv_content.setMovementMethod(LinkMovementMethod.getInstance()); 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/krytro/wordsaway/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.krytro.wordsaway; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.os.Handler; 11 | import android.os.Message; 12 | import android.text.Html; 13 | import android.view.Menu; 14 | import android.view.MenuInflater; 15 | import android.view.MenuItem; 16 | import android.view.View; 17 | import android.widget.ArrayAdapter; 18 | import android.widget.Button; 19 | import android.widget.CheckBox; 20 | import android.widget.EditText; 21 | import android.widget.LinearLayout; 22 | import android.widget.Spinner; 23 | import android.widget.TextView; 24 | import android.widget.Toast; 25 | 26 | import androidx.appcompat.app.AlertDialog; 27 | import androidx.appcompat.app.AppCompatActivity; 28 | 29 | import org.json.JSONException; 30 | import org.json.JSONObject; 31 | 32 | import java.io.BufferedReader; 33 | import java.io.IOException; 34 | import java.io.InputStream; 35 | import java.io.InputStreamReader; 36 | import java.io.UnsupportedEncodingException; 37 | import java.net.HttpURLConnection; 38 | import java.net.MalformedURLException; 39 | import java.net.URL; 40 | import java.net.URLEncoder; 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | import java.util.regex.Matcher; 44 | import java.util.regex.Pattern; 45 | 46 | 47 | 48 | public class MainActivity extends AppCompatActivity { 49 | WordsAway wa = new WordsAway(); 50 | 51 | private Menu main_menu; 52 | 53 | private EditText editText_input; 54 | private EditText editText_maxCol; 55 | private EditText editText_minRow; 56 | private CheckBox checkBox_missUrl; 57 | private CheckBox checkBox_coolapkMode; 58 | private CheckBox checkBox_zeroWidthSpace; 59 | private CheckBox checkBox_rowsReverse; 60 | private CheckBox checkBox_wordsReverse; 61 | private CheckBox checkBox_shortenUrl; 62 | private CheckBox checkBox_verticalText; 63 | private CheckBox checkBox_lettersFont; 64 | private TextView textView_result; 65 | private Spinner spinner_fonts; 66 | private Button button_processText; 67 | 68 | private LinearLayout linearLayout_verticalText_options; 69 | private LinearLayout linearLayout_fontSelect; 70 | 71 | private ClipboardManager cm; 72 | 73 | private String latestResult; 74 | 75 | private String[] fontNames = {"monospace", "script", "double-struck", "sans-serif", 76 | "sans-serif-bold", "sans-serif-italic", "sans-serif-bold-italic", "bold", "italic", 77 | "bold-italic", "bold-script", "fake-normal"}; 78 | 79 | private String currText; 80 | 81 | final static int SET_SHORTEN_URL = 0; 82 | final static int SHORTEN_URL_FAIL_TODO = 1; 83 | 84 | 85 | private int urlNum; 86 | private int urlDone; 87 | private String[] originUrls; 88 | 89 | class mHandler extends Handler { 90 | 91 | @Override 92 | public void handleMessage(Message message) { 93 | switch (message.what) { 94 | case MainActivity.SET_SHORTEN_URL: 95 | setShortenUrl((String)message.obj, originUrls[message.arg1]); 96 | break; 97 | case MainActivity.SHORTEN_URL_FAIL_TODO: 98 | shortenUrlFailTodo(); 99 | break; 100 | } 101 | } 102 | 103 | private void setShortenUrl(String data, String originUrl) { 104 | JSONObject json = null; 105 | try { 106 | json = new JSONObject(data); 107 | currText = currText.replace(originUrl, json.getString("shorturl")); 108 | } catch (JSONException e) { 109 | e.printStackTrace(); 110 | shortenUrlFailTodo(); 111 | } 112 | urlCount(); 113 | } 114 | 115 | private void shortenUrlFailTodo() { 116 | toast("短链接请求失败"); 117 | urlCount(); 118 | } 119 | 120 | private void urlCount() { 121 | if(++urlDone == urlNum) { 122 | setResult(currText); 123 | button_processText.setEnabled(true); 124 | urlNum = 0; 125 | urlDone = 0; 126 | originUrls = new String[]{}; 127 | } 128 | } 129 | } 130 | 131 | private Handler mhandler = new mHandler(); 132 | 133 | @Override 134 | protected void onCreate(Bundle savedInstanceState) { 135 | super.onCreate(savedInstanceState); 136 | setContentView(R.layout.activity_main); 137 | 138 | editText_input = findViewById(R.id.editTextTextMultiLine); 139 | editText_maxCol = findViewById(R.id.editTextNumber_maxCol); 140 | editText_minRow = findViewById(R.id.editTextNumber_minRow); 141 | checkBox_missUrl = findViewById(R.id.checkBox_missUrl); 142 | checkBox_coolapkMode = findViewById(R.id.checkBox_coolapkMode); 143 | checkBox_zeroWidthSpace = findViewById(R.id.checkBox_zeroWidthSpace); 144 | checkBox_rowsReverse = findViewById(R.id.checkBox_rowsReverse); 145 | checkBox_wordsReverse = findViewById(R.id.checkBox_wordsReverse); 146 | checkBox_shortenUrl = findViewById(R.id.checkBox_shortenUrl); 147 | checkBox_verticalText = findViewById(R.id.checkBox_verticalText); 148 | checkBox_lettersFont = findViewById(R.id.checkBox_lettersFont); 149 | textView_result = findViewById(R.id.textView_result); 150 | button_processText = findViewById(R.id.button_processText); 151 | 152 | linearLayout_verticalText_options = findViewById(R.id.linearLayout_verticalText_options); 153 | linearLayout_fontSelect = findViewById(R.id.linearLayout_fontSelect); 154 | 155 | cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 156 | 157 | spinner_fonts = findViewById(R.id.spinner_font); 158 | ArrayAdapter adapter = ArrayAdapter.createFromResource(this, 159 | R.array.fonts_array, android.R.layout.simple_spinner_item); 160 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 161 | spinner_fonts.setAdapter(adapter); 162 | } 163 | 164 | public void processText(View view) { 165 | currText = editText_input.getText().toString(); 166 | String urlRegex = "(http(s)?:\\/\\/([\\w-]+\\.)+[\\w-]+(\\/[\\w-.\\/?%&=+]*)?)"; 167 | 168 | String marked = "\ue0dc$1\ue0dd"; 169 | if (checkBox_missUrl.isChecked()) { 170 | currText = currText.replaceAll(urlRegex, marked); 171 | } 172 | if (checkBox_coolapkMode.isChecked()) { 173 | currText = currText.replaceAll("(#[\\w\\u4e00-\\u9fa5\\u3040-\\u30ff]{1,20}?#)", marked) 174 | .replaceAll("(@[\\w\\u4e00-\\u9fa5\\u3040-\\u30ff]{1,15} ?)", marked) 175 | .replaceAll("(\\[[\\w\\u4e00-\\u9fa5]{1,10}?\\])", marked); 176 | } 177 | if (checkBox_rowsReverse.isChecked()) { 178 | currText = wa.rowsReverse(currText, true); 179 | } 180 | if (checkBox_wordsReverse.isChecked()) { 181 | currText = wa.wordsReverse(currText, true); 182 | } 183 | if (checkBox_zeroWidthSpace.isChecked()) { 184 | currText = wa.mixin(currText, "\u200b", true); 185 | } 186 | if (checkBox_verticalText.isChecked()) { 187 | int maxCol = Integer.parseInt(editText_maxCol.getText().toString()); 188 | int minRow = Integer.parseInt(editText_minRow.getText().toString()); 189 | if (maxCol == 0) { 190 | toast("最大列数不能为0"); 191 | return; 192 | } 193 | currText = wa.verticalText(currText, maxCol, minRow); 194 | } 195 | spinner_fonts.getSelectedItemId(); 196 | if (checkBox_lettersFont.isChecked()) { 197 | currText = wa.font(currText, fontNames[spinner_fonts.getSelectedItemPosition()]); 198 | } 199 | currText = currText.replaceAll("\\ue0dc([^\\s]+? ?)\\ue0dd", "$1"); 200 | 201 | if (checkBox_shortenUrl.isChecked()) { 202 | String[] urls = regFindG(currText, urlRegex); 203 | if (urls.length > 0) { 204 | setResult("短链接请求中..."); 205 | button_processText.setEnabled(false); 206 | for (int i = 0; i < urls.length; i++) { 207 | String originUrl = urls[i]; 208 | originUrls = urls; 209 | urlNum = urls.length; 210 | urlDone = 0; 211 | try { 212 | String urlText = "https://is.gd/create.php?format=json&url=" 213 | + URLEncoder.encode(originUrl, "UTF-8"); 214 | URL url = new URL(urlText); 215 | 216 | Message resolve = new Message(); 217 | resolve.what = SET_SHORTEN_URL; 218 | resolve.arg1 = i; 219 | 220 | Message reject = new Message(); 221 | reject.what = SHORTEN_URL_FAIL_TODO; 222 | reject.arg1 = i; 223 | httpGet(url, resolve, reject); 224 | } catch (MalformedURLException | UnsupportedEncodingException e) { 225 | e.printStackTrace(); 226 | Message msg = new Message(); 227 | msg.what = SHORTEN_URL_FAIL_TODO; 228 | msg.arg1 = i; 229 | mhandler.sendMessage(msg); 230 | } 231 | } 232 | } else { 233 | setResult(currText); 234 | } 235 | } else { 236 | setResult(currText); 237 | } 238 | } 239 | 240 | private void setResult(String text) { 241 | textView_result.setText(text); 242 | latestResult = text; 243 | } 244 | 245 | private void httpGet(URL url, Message resolve, Message reject) { 246 | new Thread(() -> { 247 | HttpURLConnection connection = null; 248 | BufferedReader reader = null; 249 | try { 250 | connection = (HttpURLConnection) url.openConnection(); 251 | connection.setRequestMethod("GET"); 252 | connection.setConnectTimeout(5000); 253 | connection.setReadTimeout(5000); 254 | InputStream in = connection.getInputStream(); 255 | reader = new BufferedReader(new InputStreamReader(in)); 256 | StringBuilder result = new StringBuilder(); 257 | String line; 258 | while ((line = reader.readLine()) != null) { 259 | result.append(line); 260 | } 261 | 262 | resolve.obj = result.toString(); 263 | mhandler.sendMessage(resolve); 264 | } catch (IOException e) { 265 | e.printStackTrace(); 266 | reject.obj = e; 267 | mhandler.sendMessage(reject); 268 | } finally { 269 | if (reader != null) { 270 | try { 271 | reader.close(); 272 | } catch (IOException e) { 273 | e.printStackTrace(); 274 | } 275 | } 276 | if (connection != null) { 277 | connection.disconnect(); 278 | } 279 | } 280 | }).start(); 281 | } 282 | 283 | 284 | private String[] regFindG(String originalText,String regEx ) { 285 | List result = new ArrayList<>(); 286 | Pattern pat = Pattern.compile(regEx); 287 | Matcher mat = pat.matcher(originalText); 288 | while (mat.find()) { 289 | result.add(mat.group(1)); 290 | } 291 | return (result.toArray(new String[0])); 292 | } 293 | 294 | public void onCheckBoxClick(View view) { 295 | boolean isChecked = ((CheckBox)view).isChecked(); 296 | switch (view.getId()) { 297 | case R.id.checkBox_missUrl: 298 | if (isChecked) { 299 | checkBox_shortenUrl.setEnabled(true); 300 | } else { 301 | checkBox_shortenUrl.setChecked(false); 302 | checkBox_shortenUrl.setEnabled(false); 303 | } 304 | break; 305 | case R.id.checkBox_wordsReverse: 306 | if (isChecked) { 307 | checkBox_rowsReverse.setChecked(false); 308 | checkBox_zeroWidthSpace.setChecked(false); 309 | } 310 | break; 311 | case R.id.checkBox_rowsReverse: 312 | if (isChecked) { 313 | checkBox_wordsReverse.setChecked(false); 314 | } 315 | break; 316 | case R.id.checkBox_zeroWidthSpace: 317 | if (isChecked) { 318 | checkBox_wordsReverse.setChecked(false); 319 | } 320 | break; 321 | case R.id.checkBox_verticalText: 322 | if (isChecked) { 323 | linearLayout_verticalText_options.setVisibility(View.VISIBLE); 324 | } else { 325 | linearLayout_verticalText_options.setVisibility(View.GONE); 326 | } 327 | break; 328 | case R.id.checkBox_lettersFont: 329 | if (isChecked) { 330 | linearLayout_fontSelect.setVisibility(View.VISIBLE); 331 | } else { 332 | linearLayout_fontSelect.setVisibility(View.GONE); 333 | } 334 | break; 335 | } 336 | } 337 | 338 | public void copyResult(View view) { 339 | ClipData data = ClipData.newPlainText("Result", latestResult); 340 | cm.setPrimaryClip(data); 341 | toast("已复制"); 342 | } 343 | 344 | public void toast(String content) { 345 | Context context = getApplicationContext(); 346 | Toast toast = Toast.makeText(context, content, Toast.LENGTH_SHORT); 347 | toast.show(); 348 | } 349 | 350 | @Override 351 | public boolean onCreateOptionsMenu(Menu menu) { 352 | MenuInflater inflater = getMenuInflater(); 353 | inflater.inflate(R.menu.main_menu, menu); 354 | main_menu = menu; 355 | return true; 356 | } 357 | 358 | @Override 359 | public boolean onOptionsItemSelected(MenuItem item) { 360 | switch (item.getItemId()) { 361 | case R.id.menu_item_usage: 362 | showUsageDialog(); 363 | break; 364 | case R.id.menu_item_website: 365 | openUrl("https://wordsaway.texice.xyz"); 366 | break; 367 | case R.id.menu_item_about: 368 | Intent intent_about = new Intent(this, AboutActivity.class); 369 | startActivity(intent_about); 370 | break; 371 | } 372 | return true; 373 | } 374 | 375 | private void showUsageDialog() { 376 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 377 | builder.setTitle(R.string.usage_title); 378 | builder.setMessage(Html.fromHtml(getString(R.string.usage_content))); 379 | builder.setPositiveButton(R.string.usage_btn_confirm, new DialogInterface.OnClickListener() { 380 | @Override 381 | public void onClick(DialogInterface dialogInterface, int i) { 382 | dialogInterface.dismiss(); 383 | } 384 | }); 385 | builder.create().show(); 386 | } 387 | 388 | private void openUrl(String urlText) { 389 | Uri uri = Uri.parse(urlText); 390 | Intent intent_url = new Intent(); 391 | intent_url.setAction("android.intent.action.VIEW"); 392 | intent_url.setData(uri); 393 | startActivity(intent_url); 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /app/src/main/java/com/krytro/wordsaway/WordsAway.java: -------------------------------------------------------------------------------- 1 | package com.krytro.wordsaway; 2 | 3 | import java.util.*; 4 | 5 | public class WordsAway { 6 | Map>> styles = new HashMap>>(){{ 7 | put("letters", new HashMap>(){{ 8 | put("normal", stringListed("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 9 | false)); 10 | put("bold", stringListed("𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙", 11 | false)); 12 | put("italic", stringListed("𝑎𝑏𝑐𝑑𝑒𝑓𝑔𝑕𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍", 13 | false)); 14 | put("monospace", stringListed("𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉", 15 | false)); 16 | put("script", stringListed("𝒶𝒷𝒸𝒹𝑒𝒻𝑔𝒽𝒾𝒿𝓀𝓁𝓂𝓃𝑜𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜𝐵𝒞𝒟𝐸𝐹𝒢𝐻𝐼𝒥𝒦𝐿𝑀𝒩𝒪𝒫𝒬𝑅𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵", 17 | false)); 18 | put("bold-italic", stringListed("𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁", 19 | false)); 20 | put("bold-script", stringListed("𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩", 21 | false)); 22 | put("double-struck", stringListed("𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ", 23 | false)); 24 | put("sans-serif", stringListed("𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹", 25 | false)); 26 | put("sans-serif-bold", stringListed("𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭", 27 | false)); 28 | put("sans-serif-italic", stringListed("𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡", 29 | false)); 30 | put("sans-serif-bold-italic", stringListed("𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕", 31 | false)); 32 | put("reverse", stringListed("ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎzⱯꓭƆꓷꓱℲꓨHIꓩꞰꓶꟽNOꓒQꓤSꞱꓵɅMX⅄Z", 33 | false)); 34 | //实际有效:асԁеցһіјӏոорԛѕսԝхуАВСЕНІЈКМОРԚЅΤՍԜХΥΖ 35 | put("fake-normal", stringListed("аbсԁеfցһіјkӏmոорԛrѕtսvԝхуzАВСDЕFGНІЈКLМNОРԚRЅΤՍVԜХΥΖ", 36 | false)); 37 | }}); 38 | put("numbers", new HashMap>(){{ 39 | put("normal", stringListed("0123456789", false)); 40 | put("bold", stringListed("𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗", false)); 41 | put("monospace", stringListed("𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿", false)); 42 | put("sans-serif", stringListed("𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫", false)); 43 | put("double-struck", stringListed("𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡", false)); 44 | put("sans-serif-bold", stringListed("𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵", false)); 45 | }}); 46 | put("marks", new HashMap>(){{ 47 | put("normal", Arrays.asList("\\?", "\\.", ",", "!", "\\&", "\"")); 48 | put("reverse", Arrays.asList("¿", "˙", "'", "¡", "⅋", ",,")); 49 | }}); 50 | }}; 51 | 52 | public static final String[] fontNames = {"bold", "italic", "monospace", "script", "bold-italic", 53 | "bold-script", "double-struck", "sans-serif", "sans-serif-bold", "sans-serif-italic", 54 | "sans-serif-bold-italic", "reverse", "fake-normal"}; 55 | 56 | public WordsAway() { 57 | 58 | } 59 | 60 | public List stringListed(String text, boolean marks, String beforeMark, String afterMark) { 61 | List list = new ArrayList<>(); 62 | final int length = text.length(); 63 | for (int offset = 0; offset < length;) { 64 | final int codepoint = text.codePointAt(offset); 65 | list.add(String.valueOf(Character.toChars(codepoint))); 66 | offset += Character.charCount(codepoint); 67 | } 68 | List result = new ArrayList<>(); 69 | if (marks) { 70 | boolean inMarks = false; 71 | int before = 0; 72 | for (int j = 0; j < list.size(); j++) { 73 | String x = list.get(j); 74 | if (x.equals(beforeMark)) { 75 | if (inMarks) { 76 | result.addAll(list.subList(before, j)); 77 | } else { 78 | inMarks = true; 79 | before = j; 80 | } 81 | } else if (x.equals(afterMark) && inMarks) { 82 | inMarks = false; 83 | result.add(join(list.subList(before, j+1))); 84 | } else if (!inMarks){ 85 | result.add(x); 86 | } 87 | } 88 | if (inMarks) { 89 | result.addAll(list.subList(before, list.size())); 90 | } 91 | } else { 92 | result = list; 93 | } 94 | return result; 95 | } 96 | public List stringListed(String text, boolean marks) { 97 | return stringListed(text, marks, "\ue0dc", "\ue0dd"); 98 | } 99 | 100 | public String join(List list, String separator){ 101 | StringBuilder sb = new StringBuilder(); 102 | for (int i = 0; i < list.size() - 1; i++) { 103 | sb.append(list.get(i)).append(separator); 104 | } 105 | if (list.size() >= 1) { 106 | sb.append(list.get(list.size() - 1)); 107 | } 108 | return sb.toString(); 109 | } 110 | public String join(List list){ 111 | return join(list, ""); 112 | } 113 | 114 | public String mixin(String text, String mixin, boolean missMarks) { 115 | List list = stringListed(text, missMarks); 116 | return join(list, mixin); 117 | } 118 | public String mixin(String text) { 119 | return mixin(text, "\u200b", true); 120 | } 121 | 122 | public String rowsReverse(String text, boolean missMarks) { 123 | List rows = Arrays.asList(text.split("\n")); 124 | StringBuilder result = new StringBuilder(); 125 | for (int i = 0; i < rows.size(); i++) { 126 | String row = rows.get(i); 127 | List list = stringListed(row, missMarks); 128 | Collections.reverse(list); 129 | result.append("\u202e"); 130 | result.append(join(list)); 131 | if (i < rows.size() - 1) { 132 | result.append("\n"); 133 | } 134 | } 135 | return toggleBrackets(result.toString(), missMarks); 136 | } 137 | 138 | public String wordsReverse(String text, boolean missMarks) { 139 | List rows = Arrays.asList(text.split("\n")); 140 | StringBuilder result = new StringBuilder(); 141 | for (int i = 0; i < rows.size(); i++) { 142 | String row = rows.get(i); 143 | List list = stringListed(row, missMarks); 144 | for (int j = 0; j < list.size(); j += 3) { 145 | String first = list.get(j); 146 | String second = (j + 1 < list.size()) ? 147 | toggleBracketsOne(list.get(j + 1)) : 148 | ""; 149 | String third = (j + 2 < list.size()) ? 150 | toggleBracketsOne(list.get(j + 2)) : 151 | ""; 152 | result.append("\u200e" + first + "\u202e" + third + second + "\u202c"); 153 | } 154 | if (i < rows.size() - 1) { 155 | result.append("\n"); 156 | } 157 | } 158 | return result.toString(); 159 | } 160 | 161 | public String toggleBrackets(String text, boolean marks) { 162 | List list = stringListed(text, marks); 163 | StringBuilder result = new StringBuilder(); 164 | for (int i = 0; i < list.size(); i++) { 165 | result.append(toggleBracketsOne(list.get(i))); 166 | } 167 | return result.toString(); 168 | } 169 | public String toggleBrackets(String text) { 170 | return toggleBrackets(text, true); 171 | } 172 | public String toggleBracketsOne(String text) { 173 | switch (text) { 174 | case "[": 175 | return "]"; 176 | case "]": 177 | return "["; 178 | case "{": 179 | return "}"; 180 | case "}": 181 | return "{"; 182 | case "(": 183 | return ")"; 184 | case ")": 185 | return "("; 186 | case "<": 187 | return ">"; 188 | case "(": 189 | return ")"; 190 | case ")": 191 | return "("; 192 | case "《": 193 | return "》"; 194 | case "》": 195 | return "《"; 196 | case "【": 197 | return "】"; 198 | case "】": 199 | return "【"; 200 | case ">": 201 | return "<"; 202 | default: 203 | return text; 204 | } 205 | } 206 | 207 | public String verticalText(String text, int maxCol, int minHeight) { 208 | text = text.replaceAll("[\\s]", ""); 209 | int rowNum = Math.max((int)Math.ceil(text.length() / maxCol), minHeight); 210 | List list = stringListed(text, false); 211 | StringBuilder[] rows = new StringBuilder[rowNum]; 212 | for (int i = 0; i < rows.length; i++) { 213 | rows[i] = new StringBuilder(); 214 | } 215 | for (int i = 0; i < list.size(); i++) { 216 | rows[i % rowNum].append(list.get(i)).append(" "); 217 | } 218 | StringBuilder result = new StringBuilder(); 219 | for (int i = 0; i < rows.length; i++) { 220 | result.append(rows[i]).append("\n"); 221 | } 222 | return result.toString(); 223 | } 224 | public String verticalText(String text) { 225 | return verticalText(text, 12, 5); 226 | } 227 | 228 | public String replaceAll(String text, List from, List to, boolean missMarks) { 229 | StringBuilder result = new StringBuilder(); 230 | List list = stringListed(text, missMarks); 231 | for (int i = 0; i < list.size(); i++) { 232 | String x = list.get(i); 233 | boolean found = false; 234 | for (int j = 0; j < from.size(); j++) { 235 | if (x.equals(from.get(j))) { 236 | result.append(to.get(j)); 237 | found = true; 238 | } 239 | } 240 | if (!found) { 241 | result.append(x); 242 | } 243 | } 244 | return result.toString(); 245 | } 246 | 247 | public String font(String text, String from, String to, boolean missMarks) { 248 | for (Map fonts : styles.values()) { 249 | if (!(fonts.containsKey(from) && fonts.containsKey(to))) { 250 | continue; 251 | } 252 | text = replaceAll(text, (List) fonts.get(from), (List)fonts.get(to), missMarks); 253 | } 254 | return text; 255 | } 256 | public String font(String text, String to) { 257 | return font(text, "normal", to, true); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /app/src/main/res/color/btn_background_tint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 |