1
mirror of https://github.com/jlelse/teleposter synced 2024-06-24 09:37:36 +00:00

Compare commits

..

No commits in common. "master" and "1.3.1" have entirely different histories.

26 changed files with 625 additions and 972 deletions

View File

@ -8,13 +8,6 @@ For more information about Telegra.ph visit https://telegram.org/blog/instant-vi
Please take care, that this app might be unstable due to it's early development state! Please take care, that this app might be unstable due to it's early development state!
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="90">](https://f-droid.org/packages/telegra.ph/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"
height="90">](https://play.google.com/store/apps/details?id=telegra.ph)
## LICENSE ## LICENSE
``` ```

View File

@ -2,19 +2,19 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
compileSdkVersion 30 compileSdkVersion 26
buildToolsVersion "30.0.2" buildToolsVersion "26.0.1"
defaultConfig { defaultConfig {
applicationId "telegra.ph" applicationId "telegra.ph"
minSdkVersion 19 minSdkVersion 15
targetSdkVersion 30 targetSdkVersion 26
versionCode 18 versionCode 10
versionName "1.5.5" versionName "1.3.1"
resConfigs "en", "de", "es", "tr", "ru" resConfigs "en"
} }
buildTypes { buildTypes {
debug { debug {
minifyEnabled false minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
versionNameSuffix ' debug' versionNameSuffix ' debug'
@ -24,19 +24,19 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
lintOptions {
abortOnError false
}
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.android.support:appcompat-v7:26.0.2'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'com.android.support:recyclerview-v7:26.0.2'
implementation 'com.android.support:support-v13:26.0.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.github.delight-im:Android-AdvancedWebView:3.2.1' implementation 'com.github.delight-im:Android-AdvancedWebView:v3.0.0'
implementation 'com.afollestad.material-dialogs:core:3.3.0' implementation 'com.afollestad.material-dialogs:core:0.9.4.7'
implementation 'com.afollestad.material-dialogs:input:3.3.0' implementation 'com.afollestad.material-dialogs:commons:0.9.4.7'
implementation 'com.github.kittinunf.fuel:fuel:2.3.0' implementation 'pub.devrel:easypermissions:1.0.0'
implementation 'com.github.kittinunf.fuel:fuel-android:2.3.0' implementation('com.afollestad:bridge:5.1.2') {
implementation 'com.github.kittinunf.fuel:fuel-json:2.3.0' exclude group: 'org.json', module: 'json'
exclude group: 'com.intellij', module: 'annotations'
}
} }

View File

@ -32,9 +32,6 @@
<data android:scheme="http" /> <data android:scheme="http" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="telegra.ph" /> <data android:host="telegra.ph" />
<data android:host="graph.org" />
<data android:host="edit.telegra.ph" />
<data android:host="edit.graph.org" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>

View File

@ -1,118 +1,74 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<link <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.18/summernote-lite.css" rel="stylesheet">
rel="stylesheet" <link href="https://rawgit.com/summernote/summernote/master/dist/summernote.css"
/> rel="stylesheet">
<style> <style> * { max-width: 100%; height: auto; word-break: break-all; word-break: break-word; }</style>
* { </head>
max-width: 100% !important; <body>
height: auto; <div id="summernote" style="width:100%;height:100%"></div>
word-break: break-all; <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
word-break: break-word; <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
} <script src="https://rawgit.com/summernote/summernote/master/dist/summernote.min.js"></script>
<script>
#summernote { function domToNode(domNode) {
width: 100%;
height: 100%;
}
.note-editor {
border: none !important;
}
</style>
</head>
<body>
<div id="summernote"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.18/summernote-lite.min.js"></script>
<script>
function domToNode(domNode) {
if (domNode.nodeType == domNode.TEXT_NODE) { if (domNode.nodeType == domNode.TEXT_NODE) {
return domNode.data; return domNode.data;
} }
if (domNode.nodeType != domNode.ELEMENT_NODE) { if (domNode.nodeType != domNode.ELEMENT_NODE) {
return false; return false;
} }
var nodeElement = {}; var nodeElement = {};
nodeElement.tag = domNode.tagName.toLowerCase(); nodeElement.tag = domNode.tagName.toLowerCase();
for (var i = 0; i < domNode.attributes.length; i++) { for (var i = 0; i < domNode.attributes.length; i++) {
var attr = domNode.attributes[i]; var attr = domNode.attributes[i];
if (attr.name == "href" || attr.name == "src") { if (attr.name == 'href' || attr.name == 'src') {
if (!nodeElement.attrs) { if (!nodeElement.attrs) {
nodeElement.attrs = {}; nodeElement.attrs = {};
}
nodeElement.attrs[attr.name] = attr.value;
} }
nodeElement.attrs[attr.name] = attr.value;
}
} }
if (domNode.childNodes.length > 0) { if (domNode.childNodes.length > 0) {
nodeElement.children = []; nodeElement.children = [];
for (var ii = 0; ii < domNode.childNodes.length; ii++) { for (var ii = 0; ii < domNode.childNodes.length; ii++) {
var child = domNode.childNodes[ii]; var child = domNode.childNodes[ii];
nodeElement.children.push(domToNode(child)); nodeElement.children.push(domToNode(child));
} }
} }
return nodeElement; return nodeElement;
} }
function getNodeJson() { function getNodeJson() {
window.android.getText( window.android.getText(JSON.stringify(domToNode(document.getElementsByClassName('note-editable')[0]).children));
JSON.stringify( }
domToNode(document.getElementsByClassName("note-editable")[0]) </script>
.children <script>
) $(document).ready(function () {
); $('#summernote').summernote({
} height: 1200,
focus: true,
function uploadImage(file) { placeholder: '',
data = new FormData(); toolbar: [
data.append("FileUpload", file); // [groupName, [list of button]]
$.ajax({ ['style', ['bold', 'italic']],
data: data, ['para', ['ul', 'ol']],
type: "POST", ['insert', ['link']],
url: "https://telegra.ph/upload", ['history', ['undo', 'redo']],
cache: false, ['other', ['codeview']]
contentType: false, ],
processData: false, callbacks: {
success: function(data) { onInit: function (e) {
if (data) { $("#summernote").summernote("fullscreen.toggle");
$("#summernote").summernote("insertImage", data[0].src); }
} }
}
}); });
} });
</script>
$(document).ready(function() { </body>
$("#summernote").summernote({ </html>
focus: true,
placeholder: "Start writing...",
styleTags: ["p", "h3", "h4", "blockquote", "pre"],
toolbar: [
["style", ["style", "bold", "italic", "underline", "clear"]],
["para", ["ul", "ol"]],
["insert", ["link", "picture", "hr"]],
["history", ["undo", "redo"]],
["other", ["codeview"]]
],
callbacks: {
onInit: function(e) {
$("#summernote").summernote("fullscreen.toggle");
},
onImageUpload: function(files) {
uploadImage(files[0]);
}
}
});
});
function setContent(content) {
$("#summernote").summernote("code", content);
}
</script>
</body>
</html>

View File

@ -1,60 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css"
/>
<style>
* {
max-width: 100% !important;
height: auto;
word-break: break-all;
word-break: break-word;
}
</style>
</head>
<body>
<main role="main" class="container">
<div id="viewerTitle" class="mt-3"></div>
<div id="viewerAuthor"></div>
<div id="viewerViews"></div>
<div id="viewerContent"></div>
</main>
<script>
function setTitle(title) {
document.getElementById("viewerTitle").innerHTML =
"<h1>" + title + "</h1>";
}
function setAuthor(author, url) {
var viewerAuthor = document.getElementById("viewerAuthor");
if (author && url && author.length > 0 && url.length > 0)
viewerAuthor.innerHTML =
'By <a href="' + url + '">' + author + "</a><br>";
else if (author && author.length > 0)
viewerAuthor.innerHTML = "By " + author + "<br>";
else if (url && url.length > 0)
viewerAuthor.innerHTML =
'By <a href="' + url + '"><i>Author</i></a><br>';
else viewerAuthor.innerHTML = "";
}
function setViews(views) {
document.getElementById("viewerViews").innerHTML =
views + " times viewed<br><br>";
}
function setDescription(description) {
document.getElementById("viewerContent").innerHTML = description;
}
function setContent(content) {
document.getElementById("viewerContent").innerHTML = content;
}
</script>
</body>
</html>

View File

@ -0,0 +1,155 @@
package telegra.ph
import com.afollestad.bridge.Bridge
import com.afollestad.bridge.MultipartForm
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
object Api {
private val ApiBase = "https://api.telegra.ph/"
fun getPage(path: String?, accessToken: String?, callback: (success: Boolean, page: Page?) -> Unit) {
try {
Bridge.get("${ApiBase}getPage/$path?access_token=%s&return_content=true", accessToken).asString { response, _, _ ->
if (response?.isSuccess == true) callback(true, response.asAsonObject()?.toStockJson()?.parsePageResponse())
else callback(false, null)
}
} catch (e: Exception) {
callback(false, null)
}
}
fun createPage(accessToken: String?, content: String?, title: String?, name: String?, callback: (success: Boolean, Page?) -> Unit) {
try {
Bridge.get("${ApiBase}createPage?access_token=%s&title=%s&author_name=%s&content=%s&return_content=true", accessToken, title, name, content).asString { response, _, _ ->
if (response?.isSuccess == true) callback(true, response.asAsonObject()?.toStockJson()?.parsePageResponse())
else callback(false, null)
}
} catch (e: Exception) {
callback(false, null)
}
}
fun editPage(accessToken: String?, path: String?, content: String?, title: String?, name: String?, callback: (success: Boolean, Page?) -> Unit) {
try {
Bridge.get("${ApiBase}editPage/$path?access_token=%s&title=%s&author_name=%s&content=%s&return_content=true", accessToken, title, name, content).asString { response, _, _ ->
if (response?.isSuccess == true) callback(true, response.asAsonObject()?.toStockJson()?.parsePageResponse())
else callback(false, null)
}
} catch (e: Exception) {
callback(false, null)
}
}
fun createAccount(callback: (accessToken: String?) -> Unit) {
try {
Bridge.get("${ApiBase}createAccount?short_name=teleposter").asString { response, _, _ ->
if (response?.isSuccess == true) callback(response.asAsonObject()?.toStockJson()?.optJSONObject("result")?.optString("access_token"))
else callback(null)
}
} catch (e: Exception) {
callback(null)
}
}
fun getPageList(accessToken: String?, offset: Int = 0, callback: (success: Boolean, MutableList<Page>?) -> Unit) {
try {
Bridge.get("${ApiBase}getPageList?access_token=%s&limit=200&offset=$offset", accessToken).asString { response, _, _ ->
if (response?.isSuccess == true)
response.asAsonObject()?.toStockJson()?.optJSONObject("result")?.let {
val totalCount = it.optInt("total_count")
var currentCount = 200 + offset
val result = mutableListOf<Page>()
it.optJSONArray("pages")?.let {
for (i in 0 until it.length()) {
val page = it.optJSONObject(i)?.parsePage()
if (page != null) result.add(page)
}
}
if (currentCount < totalCount) {
getPageList(accessToken, currentCount) { success, pages ->
if (success && pages != null) {
result.addAll(pages)
callback(true, result)
}
if (!success) callback(false, null)
}
currentCount += 200
} else callback(true, result)
} ?: callback(false, null)
else callback(false, null)
}
} catch (e: Exception) {
callback(false, null)
}
}
private fun JSONObject.parsePageResponse(): Page? {
if (optBoolean("ok", false)) optJSONObject("result")?.let { return it.parsePage() }
return null
}
private fun JSONObject.parsePage(): Page? {
val result = Page()
result.path = optString("path", "")
result.url = optString("url", "")
result.title = optString("title", "")
result.description = optString("description", "")
result.author_name = optString("author_name", "")
result.author_url = optString("author_url", "")
result.image_url = optString("image_url", "")
optJSONArray("content")?.parseContent(result)
result.views = optInt("views", 0)
result.can_edit = optBoolean("can_edit", false)
return result
}
private fun JSONArray.parseContent(result: Page) {
for (i in 0 until length()) {
optJSONObject(i)?.let {
result.content += "<${it.optString("tag", "")}"
it.optJSONObject("attrs")?.let {
for (key in it.keys()) {
result.content += " $key=\"${it.optString(key, "")}\""
}
}
result.content += ">"
it.optJSONArray("children")?.parseContent(result)
result.content += "</${it.optString("tag", "")}>"
}
if (optJSONObject(i) == null) optString(i)?.let {
result.content += it
}
}
}
fun uploadImage(file: File, callback: (url: String?) -> Unit) {
try {
Bridge.post("http://telegra.ph/upload")
.body(MultipartForm().add("FileUpload", file))
.asJsonArray { _, jsonArray, _ ->
val src = jsonArray?.optJSONObject(0)?.optString("src", null)
callback(src)
}
} catch (e: Exception) {
callback(null)
}
}
}
class Page(
var path: String = "",
var url: String = "",
var title: String = "",
var description: String = "",
var author_name: String = "",
var author_url: String = "",
var image_url: String = "",
var content: String = "",
var views: Int = 0,
var can_edit: Boolean = false
)

View File

@ -4,37 +4,54 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import im.delight.android.webview.AdvancedWebView import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
class Editor @JvmOverloads constructor( class Editor : WebView {
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AdvancedWebView(context, attrs, defStyleAttr) {
private var getCallback: (json: String?) -> Unit? = {} private var getCallback: (json: String?) -> Unit? = {}
init { constructor(context: Context) : super(context) {
prepare() init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
init()
} }
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface") @SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
fun prepare() { private fun init() {
this.settings.javaScriptEnabled = true this.settings.javaScriptEnabled = true
this.settings.cacheMode = WebSettings.LOAD_NO_CACHE
this.addJavascriptInterface(MyJavaScriptInterface(), "android") this.addJavascriptInterface(MyJavaScriptInterface(), "android")
this.settings.loadWithOverviewMode = true this.settings.loadWithOverviewMode = true
this.settings.useWideViewPort = true this.settings.useWideViewPort = true
setMixedContentAllowed(true) this.loadDataWithBaseURL("http://telegra.ph", context.assets.open("editor.html").bufferedReader().readText(), "text/html", "utf-8", null)
this.loadDataWithBaseURL("https://telegra.ph", context.assets.open("editor.html").bufferedReader().readText(), "text/html", "utf-8", null)
} }
private inner class MyJavaScriptInterface { private inner class MyJavaScriptInterface {
@JavascriptInterface @JavascriptInterface
@SuppressWarnings("unused")
fun getText(json: String) { fun getText(json: String) {
getCallback(json) getCallback(json)
} }
} }
fun setContent(content: String?) { fun setText(html: String) {
this.loadUrl("javascript:setContent('${content?.replace("'", "\\'")}');") webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
setText(html)
}
}
this.loadUrl("javascript:$('#summernote').summernote('reset');")
this.loadUrl("javascript:$('#summernote').summernote('code', '" + html.replace("'", "\\'") + "');")
}
fun addImage(url: String) {
this.loadUrl("javascript:$('#summernote').summernote('insertImage', '$url');")
} }
fun getText(callback: (json: String?) -> Unit) { fun getText(callback: (json: String?) -> Unit) {

View File

@ -2,70 +2,24 @@ package telegra.ph
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input import com.afollestad.materialdialogs.folderselector.FileChooserDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import im.delight.android.webview.AdvancedWebView import im.delight.android.webview.AdvancedWebView
import java.net.URI import pub.devrel.easypermissions.AfterPermissionGranted
import pub.devrel.easypermissions.EasyPermissions
import java.io.File
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity(), AdvancedWebView.Listener, FileChooserDialog.FileCallback {
private val viewer: Viewer? by lazy { private val webView: AdvancedWebView? by lazy { findViewById<AdvancedWebView?>(R.id.webView) }
findViewById<Viewer?>(R.id.viewer)?.apply { private val editor: Editor? by lazy { findViewById<Editor?>(R.id.editor) }
setListener(this@MainActivity, object : AdvancedWebView.Listener {
override fun onPageFinished(url: String?) {
viewerPendingPage?.let { viewer?.showPage(it) }
viewerPendingPage = null
}
override fun onPageError(errorCode: Int, description: String?, failingUrl: String?) { private var currentUrl = ""
} private var currentPage: Page? = null
override fun onDownloadRequested(url: String?, suggestedFilename: String?, mimeType: String?, contentLength: Long, contentDisposition: String?, userAgent: String?) {
}
override fun onExternalPageRequest(url: String?) {
AdvancedWebView.Browsers.openUrl(this@MainActivity, url)
}
override fun onPageStarted(url: String?, favicon: Bitmap?) {
}
})
}
}
private var viewerPendingPage: TelegraphApi.Page? = null
private val editor: Editor? by lazy {
findViewById<Editor?>(R.id.editor)?.apply {
setListener(this@MainActivity, object : AdvancedWebView.Listener {
override fun onPageFinished(url: String?) {
editorPendingPage?.let { editor?.setContent(it.content) }
editorPendingPage = null
}
override fun onPageError(errorCode: Int, description: String?, failingUrl: String?) {
}
override fun onDownloadRequested(url: String?, suggestedFilename: String?, mimeType: String?, contentLength: Long, contentDisposition: String?, userAgent: String?) {
}
override fun onExternalPageRequest(url: String?) {
AdvancedWebView.Browsers.openUrl(this@MainActivity, url)
}
override fun onPageStarted(url: String?, favicon: Bitmap?) {
}
})
}
}
private var editorPendingPage: TelegraphApi.Page? = null
private var currentPage: TelegraphApi.Page? = null
private var editorMode = true private var editorMode = true
private var canEdit = false private var canEdit = false
private var isEdit = false private var isEdit = false
@ -73,104 +27,152 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
if (accessToken.isBlank()) TelegraphApi.createAccount(shortName = "teleposter") { success, account, error -> webView?.apply {
if (success && account != null && account.accessToken != null) { setListener(this@MainActivity, this@MainActivity)
accessToken = account.accessToken setMixedContentAllowed(true)
} else { setCookiesEnabled(true)
showError(error) setThirdPartyCookiesEnabled(true)
} addPermittedHostname("telegra.ph")
isHorizontalScrollBarEnabled = false
isVerticalScrollBarEnabled = false
overScrollMode = View.OVER_SCROLL_NEVER
} }
if (intent.action == Intent.ACTION_VIEW) { if (accessToken().isNullOrBlank()) Api.createAccount { accessToken ->
val uri = URI.create(intent.dataString) if (accessToken != null) saveAccessToken(accessToken)
when (uri.host) {
"telegra.ph", "graph.org" -> loadPage(uri.path)
"edit.telegra.ph", "edit.graph.org" -> login(uri.toString())
}
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent?.action == Intent.ACTION_VIEW) {
val uri = URI.create(intent.dataString)
when (uri.host) {
"telegra.ph", "graph.org" -> loadPage(uri.path)
"edit.telegra.ph", "edit.graph.org" -> login(uri.toString())
}
} }
if (intent.action == Intent.ACTION_VIEW && intent.dataString.contains("telegra.ph")) loadPage(intent.dataString.split("/").last())
else loadEditor()
} }
private fun loadEditor(path: String? = null) { private fun loadEditor(path: String? = null) {
editorMode = true runOnUiThread {
canEdit = false editorMode = true
isEdit = false canEdit = false
invalidateOptionsMenu() isEdit = false
editor?.visibility = View.VISIBLE invalidateOptionsMenu()
viewer?.visibility = View.GONE editor?.visibility = View.VISIBLE
currentPage = null webView?.visibility = View.GONE
// Load currentPage = null
if (path != null) TelegraphApi.getPage(accessToken, path, true) { success, page, error -> // Load
if (success && page != null) { if (path != null) Api.getPage(path, accessToken()) { success, page ->
isEdit = true if (success) runOnUiThread {
currentPage = page isEdit = true
editorPendingPage = page currentPage = page
editor?.prepare() editor?.setText(page?.content ?: "")
} else { }
showError(error) else showError()
} }
} else {
editor?.prepare()
}
}
private fun login(authUrl: String) {
TelegraphApi.login(authUrl) { success, accessToken, account ->
if (success && !accessToken.isNullOrEmpty()) {
this.accessToken = accessToken
this.authorName = account?.authorName
showMessage(getString(R.string.success), getString(R.string.login_success))
} else showError(getString(R.string.login_failed))
} }
} }
private fun loadPage(path: String) { private fun loadPage(path: String) {
editorMode = false runOnUiThread {
canEdit = false editorMode = false
invalidateOptionsMenu() canEdit = false
viewer?.visibility = View.VISIBLE invalidateOptionsMenu()
editor?.visibility = View.GONE webView?.visibility = View.VISIBLE
currentPage = null editor?.visibility = View.GONE
// Load currentPage = null
TelegraphApi.getPage(accessToken, path, true) { success, page, error -> // Load
if (success && page != null) { Api.getPage(path, accessToken()) { success, page ->
canEdit = page.canEdit ?: false if (success) showPage(page)
invalidateOptionsMenu() else showError()
currentPage = page
viewerPendingPage = page
viewer?.prepare()
} }
else showError(error)
} }
} }
private fun showError(message: String? = null) = showMessage(getString(R.string.error), message private fun showPage(page: Page?) {
?: getString(R.string.error_desc)) runOnUiThread {
editorMode = false
canEdit = page?.can_edit ?: false
invalidateOptionsMenu()
webView?.visibility = View.VISIBLE
editor?.visibility = View.GONE
currentPage = page
webView?.clearHistory()
// Show
page?.let {
var html = getString(R.string.viewer_html_head)
html += "<h1>${it.title}</h1>"
if (!it.author_name.isEmpty() && !it.author_url.isBlank()) html += "<a href=\"${it.author_url}\">${it.author_name}</a><br>"
else if (!it.author_name.isEmpty()) html += "${it.author_name}<br>"
if (it.views != 0) html += "${it.views} times viewed<br><br>"
if (it.content.isBlank()) html += it.description.replace("\n", "<br>") else html += it.content
html += getString(R.string.viewer_html_end)
webView?.loadDataWithBaseURL(it.url, html, "text/html; charset=UTF-8", null, null)
currentUrl = it.url
}
}
}
private fun showMessage(title: String? = null, message: String? = null) { private fun showError() {
MaterialDialog(this@MainActivity) runOnUiThread {
.title(text = title ?: "") MaterialDialog.Builder(this)
.message(text = message ?: "") .title(R.string.error)
.positiveButton(android.R.string.ok) .content(R.string.error_desc)
.show() .positiveText(android.R.string.ok)
.show()
}
}
@AfterPermissionGranted(100)
private fun uploadImage() {
if (EasyPermissions.hasPermissions(this, "android.permission.READ_EXTERNAL_STORAGE")) {
FileChooserDialog.Builder(this)
.mimeType("image/*")
.show(this)
} else {
EasyPermissions.requestPermissions(this, "", 100, "android.permission.READ_EXTERNAL_STORAGE")
}
}
override fun onPageFinished(url: String?) {
}
override fun onPageStarted(url: String?, favicon: Bitmap?) {
}
override fun onPageError(errorCode: Int, description: String?, failingUrl: String?) {
}
override fun onDownloadRequested(url: String?, suggestedFilename: String?, mimeType: String?, contentLength: Long, contentDisposition: String?, userAgent: String?) {
}
override fun onExternalPageRequest(url: String?) {
AdvancedWebView.Browsers.openUrl(this, url)
}
override fun onFileSelection(p0: FileChooserDialog, file: File) {
Api.uploadImage(file) { src ->
if (src != null) editor?.addImage(src)
}
}
override fun onFileChooserDismissed(p0: FileChooserDialog) {
}
override fun onResume() {
super.onResume()
webView?.onResume()
}
override fun onPause() {
webView?.onPause()
super.onPause()
}
override fun onDestroy() {
webView?.onDestroy()
super.onDestroy()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
editor?.onActivityResult(requestCode, resultCode, data) webView?.onActivityResult(requestCode, resultCode, data)
} }
override fun onBackPressed() { override fun onBackPressed() {
if (viewer?.onBackPressed() == false) return if (webView?.onBackPressed() == false) return
else super.onBackPressed() else super.onBackPressed()
} }
@ -193,35 +195,34 @@ class MainActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.image -> {
uploadImage()
true
}
R.id.create -> { R.id.create -> {
loadEditor() loadEditor()
true true
} }
R.id.publish -> { R.id.publish -> {
editor?.getText { json -> editor?.getText { json ->
MaterialDialog(this@MainActivity) MaterialDialog.Builder(this)
.title(R.string.title_question) .title(R.string.title_question)
.input(hintRes = R.string.title_hint, prefill = currentPage?.title .input(getString(R.string.title_hint), currentPage?.title ?: "", { _, title ->
?: "", allowEmpty = false) { _, title -> MaterialDialog.Builder(this)
MaterialDialog(this@MainActivity)
.title(R.string.name_question) .title(R.string.name_question)
.input(hintRes = R.string.name_hint, prefill = if (isEdit) currentPage?.authorName .input(getString(R.string.name_hint), if (isEdit) currentPage?.author_name ?: authorName() ?: "" else authorName() ?: "", { _, name ->
?: authorName ?: "" else authorName if (!isEdit) saveAuthorName(name.toString())
?: "", allowEmpty = true) { _, name -> if (isEdit) Api.editPage(accessToken(), currentPage?.path, json, title.toString(), name.toString()) { success, page ->
if (!isEdit) authorName = name.toString() if (success) showPage(page)
if (isEdit) TelegraphApi.editPage(accessToken, currentPage?.path else showError()
?: "", authorName = name.toString(), title = title.toString(), content = json
?: "", returnContent = true) { success, page, error ->
if (success && page != null) loadPage(page.path)
else showError(error)
} else TelegraphApi.createPage(accessToken, content = json
?: "", title = title.toString(), authorName = name.toString(), returnContent = true) { success, page, error ->
if (success && page != null) loadPage(page.path)
else showError(error)
} }
} else Api.createPage(accessToken(), json, title.toString(), name.toString()) { success, page ->
if (success) showPage(page)
else showError()
}
})
.show() .show()
} })
.show() .show()
} }
true true
@ -231,59 +232,50 @@ class MainActivity : AppCompatActivity() {
true true
} }
R.id.bookmarks -> { R.id.bookmarks -> {
MaterialDialog(this@MainActivity) MaterialDialog.Builder(this)
.title(R.string.bookmarks) .title(R.string.bookmarks)
.positiveButton(R.string.open) .positiveText(android.R.string.ok)
.negativeButton(android.R.string.cancel) .items(bookmarks().reversed().map { it.split("xxx;xxx")[1] })
.listItemsSingleChoice(items = bookmarks().reversed().map { it.second }) { _, index, _ -> .itemsCallback { _, _, i, _ ->
loadPage(bookmarks().reversed().map { it.first }[index]) loadPage(bookmarks().reversed().map { it.split("xxx;xxx")[0] }[i])
} }
.show() .itemsLongCallback { _, _, i, _ ->
true MaterialDialog.Builder(this)
}
R.id.delete_bookmark -> {
MaterialDialog(this@MainActivity)
.title(R.string.delete_bookmark)
.positiveButton(R.string.delete)
.negativeButton(android.R.string.cancel)
.listItemsMultiChoice(items = bookmarks().reversed().map { it.second }) { _, indices, _ ->
MaterialDialog(this@MainActivity)
.title(R.string.delete) .title(R.string.delete)
.message(R.string.delete_question) .content(R.string.delete_question)
.positiveButton(R.string.yes) .positiveText(android.R.string.yes)
.negativeButton(R.string.no) .negativeText(android.R.string.no)
.positiveButton { .onPositive { _, _ ->
val tmpBookmarks = bookmarks().reversed().map { it.first } deleteBookmark(bookmarks().reversed().map { it.split("xxx;xxx")[0] }[i])
for (index in indices) deleteBookmark(tmpBookmarks[index])
} }
.show() .show()
true
} }
.show() .show()
true true
} }
R.id.published -> { R.id.published -> {
TelegraphApi.getPageList(accessToken) { success, pageList, error -> Api.getPageList(accessToken()) { success, result ->
if (success && pageList != null && pageList.pages != null) { if (!success || result == null || result.isEmpty()) showError()
MaterialDialog(this@MainActivity) else {
MaterialDialog.Builder(this)
.title(R.string.published) .title(R.string.published)
.positiveButton(R.string.open) .positiveText(android.R.string.ok)
.negativeButton(android.R.string.cancel) .items(result.map(Page::title))
.listItemsSingleChoice(items = pageList.pages.map { it.title }) { _, i, _ -> .itemsCallback { _, _, i, _ ->
loadPage(pageList.pages[i].path) loadPage(result.map(Page::path)[i])
} }
.show() .show()
} else showError(error) }
} }
true true
} }
R.id.bookmark -> { R.id.bookmark -> {
MaterialDialog(this@MainActivity) MaterialDialog.Builder(this)
.title(R.string.title_question) .title(R.string.title_question)
.input(hintRes = R.string.title_hint, prefill = currentPage?.title .input(getString(R.string.title_hint), "", { _, input ->
?: "", allowEmpty = false) { _, input -> addBookmark("${currentUrl.split("/").last()}xxx;xxx$input")
val curPage = currentPage })
if (curPage?.url != null) addBookmark(curPage.url.split("/").last(), input.toString())
}
.show() .show()
true true
} }
@ -291,28 +283,25 @@ class MainActivity : AppCompatActivity() {
val shareIntent = Intent() val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "text/plain" shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TITLE, currentPage?.title) shareIntent.putExtra(Intent.EXTRA_TITLE, webView?.title)
shareIntent.putExtra(Intent.EXTRA_TEXT, currentPage?.url) shareIntent.putExtra(Intent.EXTRA_TEXT, currentUrl)
startActivity(Intent.createChooser(shareIntent, getString(R.string.share))) startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
true true
} }
R.id.about -> { R.id.help -> {
val aboutIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/jlelse/teleposter")) MaterialDialog.Builder(this)
startActivity(aboutIntent) .title(R.string.help)
true .content(R.string.help_text, true)
} .positiveText(android.R.string.ok)
R.id.login -> {
MaterialDialog(this@MainActivity)
.title(R.string.login)
.message(R.string.login_desc)
.positiveButton(android.R.string.ok)
.positiveButton {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/telegraph")))
}
.show() .show()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
} }

View File

@ -1,42 +1,28 @@
package telegra.ph package telegra.ph
import android.content.Context import android.content.Context
import androidx.preference.PreferenceManager import android.preference.PreferenceManager
const val listItemSeparator = "+++;+++" fun Context.bookmarks(): MutableList<String> = PreferenceManager.getDefaultSharedPreferences(this).getString("bookmarks", null)?.split("+++;+++")?.toMutableList() ?: mutableListOf("apixxx;xxxAPI Documentation")
const val itemSeparator = "xxx;xxx"
fun Context.bookmarks(): List<Pair<String, String>> { fun Context.addBookmark(entry: String) {
val list = PreferenceManager.getDefaultSharedPreferences(this).getString("bookmarks", null)?.split(listItemSeparator)?.map { PreferenceManager.getDefaultSharedPreferences(this).edit().putString("bookmarks", bookmarks().apply {
val splitParts = it.split(itemSeparator) add(entry)
if (splitParts.size == 2) splitParts[0] to splitParts[1] }.joinToString(separator = "+++;+++")).apply()
else null
}?.filterNotNull()
return if (list != null && list.isNotEmpty()) list else listOf("api" to "API Documentation")
}
fun Context.addBookmark(path: String, title: String) {
saveBookmarks(bookmarks().plus(path to title))
} }
fun Context.deleteBookmark(path: String) { fun Context.deleteBookmark(path: String) {
saveBookmarks(bookmarks().filter { it.first != path }) PreferenceManager.getDefaultSharedPreferences(this).edit().putString("bookmarks", bookmarks().filter { !it.contains(path) }.joinToString(separator = "+++;+++")).apply()
} }
fun Context.saveBookmarks(bookmarks: List<Pair<String, String>>) { fun Context.accessToken(): String? = PreferenceManager.getDefaultSharedPreferences(this).getString("accessToken", null)
PreferenceManager.getDefaultSharedPreferences(this).edit().putString("bookmarks",
bookmarks.joinToString(separator = listItemSeparator) { "${it.first}$itemSeparator${it.second}" } fun Context.saveAccessToken(token: String) {
).apply() PreferenceManager.getDefaultSharedPreferences(this).edit().putString("accessToken", token).apply()
} }
var Context.accessToken: String fun Context.authorName(): String? = PreferenceManager.getDefaultSharedPreferences(this).getString("authorName", null)
get() = PreferenceManager.getDefaultSharedPreferences(this).getString("accessToken", "") as String
set(value) {
PreferenceManager.getDefaultSharedPreferences(this).edit().putString("accessToken", value).apply()
}
var Context.authorName: String? fun Context.saveAuthorName(name: String) {
get() = PreferenceManager.getDefaultSharedPreferences(this).getString("authorName", null) PreferenceManager.getDefaultSharedPreferences(this).edit().putString("authorName", name).apply()
set(value) { }
PreferenceManager.getDefaultSharedPreferences(this).edit().putString("authorName", value).apply()
}

View File

@ -1,191 +0,0 @@
package telegra.ph
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.FuelManager
import com.github.kittinunf.fuel.core.Request
import com.github.kittinunf.fuel.core.Response
import com.github.kittinunf.fuel.core.interceptors.redirectResponseInterceptor
import com.github.kittinunf.fuel.httpPost
import com.github.kittinunf.fuel.json.FuelJson
import com.github.kittinunf.fuel.json.responseJson
import com.github.kittinunf.result.Result
import org.json.JSONArray
import org.json.JSONObject
import java.net.HttpCookie
object TelegraphApi {
private var loginAccessToken: String? = null
init {
FuelManager.instance.basePath = "https://api.telegra.ph"
FuelManager.instance.removeAllResponseInterceptors()
FuelManager.instance.addResponseInterceptor {
telegraphLoginInterceptor()
}
// Fix login
FuelManager.instance.addResponseInterceptor {
redirectResponseInterceptor(FuelManager.instance)
it
}
}
private fun callService(method: String, parameters: List<Pair<String, Any?>>, handler: (Request, Response, Result<FuelJson, FuelError>) -> Unit) {
val requestObject = JSONObject()
parameters.forEach {
requestObject.put(it.first, it.second)
}
method.httpPost().header(mapOf("Content-Type" to "application/json")).body(requestObject.toString()).responseJson(handler)
}
fun createAccount(shortName: String, authorName: String? = null, authorUrl: String? = null, callback: (success: Boolean, account: Account?, error: String?) -> Unit) {
callService("/createAccount", listOf("short_name" to shortName, "author_name" to authorName, "author_url" to authorUrl)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, Account(obj), null) }
}
}
fun editAccountInfo(accessToken: String, shortName: String? = null, authorName: String? = null, authorUrl: String? = null, callback: (success: Boolean, account: Account?, error: String?) -> Unit) {
callService("/editAccountInfo", listOf("access_token" to accessToken, "short_name" to shortName, "author_name" to authorName, "author_url" to authorUrl)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, Account(obj), null) }
}
}
fun getAccountInfo(accessToken: String, fields: Array<String>? = null, callback: (success: Boolean, account: Account?, error: String?) -> Unit) {
callService("/getAccountInfo", listOf("access_token" to accessToken, "fields" to fields)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, Account(obj), null) }
}
}
fun revokeAccessToken(accessToken: String, callback: (success: Boolean, account: Account?, error: String?) -> Unit) {
callService("/revokeAccessToken", listOf("access_token" to accessToken)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, Account(obj), null) }
}
}
fun createPage(accessToken: String, title: String, authorName: String? = null, authorUrl: String? = null, content: String, returnContent: Boolean? = null, callback: (success: Boolean, page: Page?, error: String?) -> Unit) {
callService("/createPage", listOf("access_token" to accessToken, "title" to title, "author_name" to authorName, "author_url" to authorUrl, "content" to content, "return_content" to returnContent)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, Page(obj), null) }
}
}
fun editPage(accessToken: String, path: String, title: String, authorName: String? = null, authorUrl: String? = null, content: String, returnContent: Boolean? = null, callback: (success: Boolean, page: Page?, error: String?) -> Unit) {
callService("/editPage", listOf("access_token" to accessToken, "path" to path, "title" to title, "author_name" to authorName, "author_url" to authorUrl, "content" to content, "return_content" to returnContent)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, Page(obj), null) }
}
}
fun getPage(accessToken: String? = null, path: String, returnContent: Boolean? = null, callback: (success: Boolean, page: Page?, error: String?) -> Unit) {
callService("/getPage", listOf("access_token" to accessToken, "path" to path, "return_content" to returnContent)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, Page(obj), null) }
}
}
fun getPageList(accessToken: String, offset: Int? = null, limit: Int? = null, callback: (success: Boolean, page: PageList?, error: String?) -> Unit) {
callService("/getPageList", listOf("access_token" to accessToken, "offset" to offset, "limit" to limit)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, PageList(obj), null) }
}
}
fun getViews(path: String, year: Int? = null, month: Int? = null, day: Int? = null, hour: Int? = null, callback: (success: Boolean, page: PageViews?, error: String?) -> Unit) {
callService("/getViews", listOf("path" to path, "year" to year, "month" to month, "day" to day, "hour" to hour)) { _, _, result ->
handleResponse(result, callback) { obj: JSONObject -> callback(true, PageViews(obj), null) }
}
}
class Account(json: JSONObject) {
val shortName: String? = json.optString("short_name")
val authorName: String? = json.optString("author_name")
val authorUrl: String? = json.optString("author_url")
val accessToken: String? = json.optString("access_token")
val authUrl: String? = json.optString("auth_url")
val pageCount: Int? = json.optInt("page_count")
}
class PageList(json: JSONObject) {
val totalCount: Int? = json.optInt("total_count")
val pages: Array<Page>? = json.optJSONArray("pages")?.let {
mutableListOf<Page>().apply { for (i in 0 until it.length()) add(Page(it.optJSONObject(i))) }.toTypedArray()
}
}
class Page(json: JSONObject) {
val path: String = json.optString("path")
val url: String = json.optString("url")
val title: String = json.optString("title")
val description: String = json.optString("description")
val authorName: String? = json.optString("author_name")
val authorUrl: String? = json.optString("author_url")
val imageUrl: String? = json.optString("image_url")
val content: String? = json.optJSONArray("content")?.parseContent()
val views: Int = json.optInt("views")
val canEdit: Boolean? = json.optBoolean("can_edit")
private fun JSONArray.parseContent(): String? {
var content = ""
for (i in 0 until length()) {
optJSONObject(i)?.let {
content += "<${it.optString("tag", "")}"
it.optJSONObject("attrs")?.let {
for (key in it.keys()) {
content += " $key=\"${it.optString(key, "")}\""
}
}
content += ">"
content += it.optJSONArray("children")?.parseContent() ?: ""
content += "</${it.optString("tag", "")}>"
}
if (optJSONObject(i) == null) optString(i)?.let {
content += it
}
// Fix mixed content
content = content.replace("http://telegra.ph", "https://telegra.ph")
content = content.replace("http://graph.org", "https://graph.org")
}
return content
}
}
class PageViews(json: JSONObject) {
val views: Int? = json.optInt("views")
}
// Teleposter
private fun <T> handleResponse(result: Result<FuelJson, FuelError>, handler: (success: Boolean, obj: T?, error: String?) -> Unit, callback: (obj: JSONObject) -> Unit) {
val (json, error) = result
if (error == null && json != null) {
val jsonObj = json.obj()
if (jsonObj.optBoolean("ok")) {
callback(jsonObj.optJSONObject("result"))
} else {
handler(false, null, jsonObj.optString("error"))
}
} else {
handler(false, null, error?.message)
}
}
// Dirty hacks
fun login(authUrl: String, callback: (success: Boolean, accessToken: String?, account: Account?) -> Unit) {
loginAccessToken = null
authUrl.httpPost().response { _, _, _ ->
if (loginAccessToken != null) getAccountInfo(accessToken = loginAccessToken!!) { success, account, _ ->
if (success) callback(true, loginAccessToken, account)
else callback(false, null, null)
} else callback(false, null, null)
}
}
private fun telegraphLoginInterceptor(): (Request, Response) -> Response =
{ _, response ->
response.headers["Set-Cookie"]
.flatMap { HttpCookie.parse(it) }
.find { it.name == "tph_token" }
?.let {
loginAccessToken = it.value
}
response
}
}

View File

@ -1,57 +0,0 @@
package telegra.ph
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.webkit.JavascriptInterface
import im.delight.android.webview.AdvancedWebView
class Viewer @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AdvancedWebView(context, attrs, defStyleAttr) {
init {
prepare()
}
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
fun prepare() {
settings.javaScriptEnabled = true
settings.loadWithOverviewMode = true
settings.useWideViewPort = true
overScrollMode = View.OVER_SCROLL_NEVER
setMixedContentAllowed(true)
loadDataWithBaseURL("https://telegra.ph", context.assets.open("viewer.html").bufferedReader().readText(), "text/html", "utf-8", null)
}
fun showPage(page: TelegraphApi.Page) {
clearHistory()
setArticleTitle(page.title)
setAuthor(page.authorName, page.authorUrl)
setViews(page.views)
if (page.content == null) setDescription(page.description)
else setContent(page.content)
}
private fun setArticleTitle(title: String) {
this.loadUrl("javascript:setTitle('$title');")
}
private fun setAuthor(author: String?, url: String?) {
this.loadUrl("javascript:setAuthor('$author','$url');")
}
private fun setViews(views: Int) {
this.loadUrl("javascript:setViews('$views');")
}
private fun setDescription(description: String) {
this.loadUrl("javascript:setDescription('${description.replace("\n", "<br>")}');")
}
private fun setContent(content: String?) {
this.loadUrl("javascript:setContent('${content?.replace("'", "\\'")}');")
}
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
@ -7,10 +8,14 @@
<telegra.ph.Editor <telegra.ph.Editor
android:id="@+id/editor" android:id="@+id/editor"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"/>
<telegra.ph.Viewer <im.delight.android.webview.AdvancedWebView
android:id="@+id/viewer" android:id="@+id/webView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"/>
</LinearLayout> </LinearLayout>

View File

@ -1,48 +1,44 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/image"
android:title="@string/add_image"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/create" android:id="@+id/create"
android:title="@string/create" android:title="@string/create"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/publish" android:id="@+id/publish"
android:title="@string/publish" android:title="@string/publish"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/edit" android:id="@+id/edit"
android:title="@string/edit" android:title="@string/edit"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/try_edit" android:id="@+id/try_edit"
android:title="@string/try_edit" android:title="@string/try_edit"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/bookmark" android:id="@+id/bookmark"
android:title="@string/bookmark_this" android:title="@string/bookmark_this"
app:showAsAction="never" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/bookmarks" android:id="@+id/bookmarks"
android:title="@string/bookmarks" android:title="@string/bookmarks"
app:showAsAction="never" /> app:showAsAction="ifRoom"/>
<item
android:id="@+id/delete_bookmark"
android:title="@string/delete_bookmark"
app:showAsAction="never" />
<item <item
android:id="@+id/published" android:id="@+id/published"
android:title="@string/published" android:title="@string/published"
app:showAsAction="never" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/share" android:id="@+id/share"
android:title="@string/share" android:title="@string/share"
app:showAsAction="never" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/login" android:id="@+id/help"
android:title="@string/login" android:title="@string/help"
app:showAsAction="never" /> app:showAsAction="ifRoom"/>
<item
android:id="@+id/about"
android:title="@string/about"
app:showAsAction="never" />
</menu> </menu>

View File

@ -1,29 +0,0 @@
<resources>
<string name="app_name">Teleposter</string>
<string name="yes">Ja</string>
<string name="no">Nein</string>
<string name="share">Teilen</string>
<string name="about">Über</string>
<string name="bookmarks">Lesezeichen</string>
<string name="bookmark_this">Als Lesezeichen setzen</string>
<string name="delete_bookmark">Lesezeichen löschen</string>
<string name="create">Neu</string>
<string name="title_question">Titel?</string>
<string name="title_hint">Fantastischer Post #1</string>
<string name="delete">Löschen</string>
<string name="delete_question">Wirklich löschen?</string>
<string name="publish">Veröffentlichen</string>
<string name="edit">Bearbeiten</string>
<string name="try_edit">Versuchen zu bearbeiten</string>
<string name="error">Fehler</string>
<string name="error_desc">Etwas unerwartetes ist passiert!</string>
<string name="published">Veröffentlichte Posts</string>
<string name="name_question">Dein Name?</string>
<string name="name_hint">Fantastischer Autor</string>
<string name="login_failed">Login fehlgeschlagen. Noch einmal versuchen!</string>
<string name="success">Erfolg</string>
<string name="login_success">Erfolgreich eingeloggt!</string>
<string name="login">Login</string>
<string name="login_desc">Öffne den Telegraph Bot in Telegram, wähle \"Login as * on this device\" und wähle Teleposter.</string>
<string name="open">Öffnen</string>
</resources>

View File

@ -1,29 +0,0 @@
<resources>
<string name="app_name">Teleposter</string>
<string name="yes"></string>
<string name="no">No</string>
<string name="share">Compartir</string>
<string name="about">Acerca de</string>
<string name="bookmarks">Favoritos</string>
<string name="bookmark_this">Añadir a favoritos</string>
<string name="delete_bookmark">Borrar favorito</string>
<string name="create">Nueva</string>
<string name="title_question">¿Título?</string>
<string name="title_hint">Entrada increíble #1</string>
<string name="delete">Borrar</string>
<string name="delete_question">¿Realmente quiere eliminar esto?</string>
<string name="publish">Publicar</string>
<string name="edit">Editar</string>
<string name="try_edit">Prueba a editar.</string>
<string name="error">Error</string>
<string name="error_desc">¡Pasó algo inesperado!</string>
<string name="published">Entradas publicadas</string>
<string name="name_question">¿Su nombre?</string>
<string name="name_hint">Awesome Writer</string>
<string name="login_failed">Login fallido. ¡Inténtelo de nuevo!</string>
<string name="success">Hecho</string>
<string name="login_success">¡Login realizado correctamente!</string>
<string name="login">Login</string>
<string name="login_desc">Abre el bot de Telegraph en Telegram, elija \"Login as * on this device\" y seleccione Teleposter.</string>
<string name="open">Abrir</string>
</resources>

View File

@ -1,29 +0,0 @@
<resources>
<string name="app_name">Teleposter</string>
<string name="yes">да</string>
<string name="no">Нет</string>
<string name="share">Поделиться</string>
<string name="about">Информация</string>
<string name="bookmarks">Закладки</string>
<string name="bookmark_this">Добавить в закладки</string>
<string name="delete_bookmark">Удалить закладку</string>
<string name="create">Новый</string>
<string name="title_question">Заглавие?</string>
<string name="title_hint">Крутой пост #1</string>
<string name="delete">Удалить</string>
<string name="delete_question">Вы действительно хотите удалить это?</string>
<string name="publish">Публикация</string>
<string name="edit">Изменить</string>
<string name="try_edit">Повторить изменение</string>
<string name="error">Ошибка</string>
<string name="error_desc">Случилось что-то неожиданное!</string>
<string name="published">Опубликованные посты</string>
<string name="name_question">Ваше имя?</string>
<string name="name_hint">Крутой писатель</string>
<string name="login_failed">Неудачный вход. Попробуй еще раз!</string>
<string name="success">Готово</string>
<string name="login_success">Вы успешно вошли!</string>
<string name="login">Вход</string>
<string name="login_desc">Откройте Telegraph bot в Telegram, выберите \"Login as * on this device\" и выберите Teleposter.</string>
<string name="open">Открыть</string>
</resources>

View File

@ -1,29 +0,0 @@
<resources>
<string name="app_name">Teleposter</string>
<string name="yes">Evet</string>
<string name="no">Hayır</string>
<string name="share">Paylaş</string>
<string name="about">Hakkında</string>
<string name="bookmarks">Yer imleri</string>
<string name="bookmark_this">Buna yer işareti koy</string>
<string name="delete_bookmark">Yer işaretini sil</string>
<string name="create">Yeni</string>
<string name="title_question">Başlık?</string>
<string name="title_hint">Harika mesaj #1</string>
<string name="delete">Sil</string>
<string name="delete_question">Bunu gerçekten silmek istiyor musun?</string>
<string name="publish">Yayınla</string>
<string name="edit">Düzenle</string>
<string name="try_edit">Düzenlemeye çalış</string>
<string name="error">Hata</string>
<string name="error_desc">Beklenmedik bir şey oldu!</string>
<string name="published">Mesaj yayınlandı</string>
<string name="name_question">Adınız?</string>
<string name="name_hint">Süper yazar</string>
<string name="login_failed">Giriş başarısız. Tekrar deneyin!</string>
<string name="success">Başarılı</string>
<string name="login_success">Başarıyla giriş yaptınız!</string>
<string name="login">Giriş</string>
<string name="login_desc">Telegram\'da Telegraph botunu açın, \"Login as * on this device\" seçeneğini ve Teleposter\'ı seçin.</string>
<string name="open"></string>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="primaryColor">#fafafa</color> <color name="colorPrimary">#3F51B5</color>
<color name="primaryDarkColor">#c7c7c7</color> <color name="colorPrimaryDark">#303F9F</color>
<color name="primaryTextColor">#000000</color> <color name="colorAccent">#FF4081</color>
</resources> </resources>

View File

@ -1,29 +1,29 @@
<resources> <resources>
<string name="app_name">Teleposter</string> <string name="app_name">Teleposter</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="share">Share</string> <string name="share">Share</string>
<string name="about">About</string> <string name="help">Help</string>
<string name="help_text">
<![CDATA[<h3>Why can\'t I select headings?</h3>Unfortunately I wasn\'t able to add the feature yet, but you can do it manually via the code editor.<h3>Why can\'t I login with my Telegram account?</h3>That\'s not possible!<h3>Used libraries</h3><a href=\"https://github.com/afollestad/material-dialogs\">Material Dialogs</a>, <a href=\"https://github.com/afollestad/bridge\">Bridge</a>, <a href=\"https://github.com/delight-im/Android-AdvancedWebView\">AdvancedWebView</a>, <a href=\"https://github.com/googlesamples/easypermissions\">EasyPermissions</a><h3>About</h3>This app is made by <a href="https://jlelse.eu">Jan-Lukas Else</a> and it\'s code is published on <a href="https://github.com/jlelse/teleposter">Github</a>.]]></string>
<string name="bookmarks">Bookmarks</string> <string name="bookmarks">Bookmarks</string>
<string name="bookmark_this">Bookmark this</string> <string name="bookmark_this">Bookmark this</string>
<string name="delete_bookmark">Delete Bookmark</string> <string name="create">Create</string>
<string name="create">New</string>
<string name="title_question">Title?</string> <string name="title_question">Title?</string>
<string name="title_hint">Awesome Post #1</string> <string name="title_hint">Awesome Post #1</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="delete_question">Do you really want to delete this?</string> <string name="delete_question">Do you really want to delete this?</string>
<string name="publish">Publish</string> <string name="publish">Publish</string>
<string name="edit">Edit</string> <string name="edit">Edit (safe)</string>
<string name="try_edit">Try to edit</string> <string name="try_edit">Edit (unsafe)</string>
<string name="error">Error</string> <string name="error">Error</string>
<string name="error_desc">Something unexpected happened!</string> <string name="error_desc">Something unexpected happened!</string>
<string name="viewer_html_head">
<![CDATA[<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\"><style> * { max-width: 100%; height: auto; word-break: break-all; word-break: break-word; }</style></head><body>]]>
</string>
<string name="viewer_html_end">
<![CDATA[<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js\"></script><script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\"></script></body></html>]]>
</string>
<string name="published">Published posts</string> <string name="published">Published posts</string>
<string name="name_question">Your name?</string> <string name="name_question">Your name?</string>
<string name="name_hint">Awesome Writer</string> <string name="name_hint">Awesome Writer</string>
<string name="login_failed">Login failed. Try again!</string> <string name="add_image">Add image</string>
<string name="success">Success</string>
<string name="login_success">You successfully logged in!</string>
<string name="login">Login</string>
<string name="login_desc">Open the Telegraph bot in Telegram, select \"Login as * on this device\" and choose Teleposter.</string>
<string name="open">Open</string>
</resources> </resources>

View File

@ -1,10 +1,10 @@
<resources> <resources>
<style name="AppTheme" parent="Theme.AppCompat.Light"> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/primaryColor</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/primaryDarkColor</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:textColorPrimary">@color/primaryTextColor</item> <item name="colorAccent">@color/colorAccent</item>
<item name="md_color_button_text">@color/primaryTextColor</item> <item name="android:colorBackground">@android:color/white</item>
</style> </style>
</resources> </resources>

View File

@ -1,18 +1,18 @@
buildscript { buildscript {
ext.kotlin_version = '1.4.10' ext.kotlin_version = '1.1.4-3'
repositories { repositories {
jcenter() jcenter()
mavenCentral() mavenCentral()
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:3.0.0-beta5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
plugins { plugins {
id 'com.github.ben-manes.versions' version '0.33.0' id 'com.github.ben-manes.versions' version '0.15.0'
} }
allprojects { allprojects {

View File

@ -1,6 +1,6 @@
kotlin.incremental=true kotlin.incremental=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx3072M org.gradle.jvmargs=-Xmx3072M
android.enableD8=true android.enableD8=true
android.useAndroidX=true android.enableAapt2=false
android.enableJetifier=true

Binary file not shown.

View File

@ -1,5 +1,6 @@
#Wed Sep 13 20:51:10 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip

53
gradlew vendored
View File

@ -1,21 +1,5 @@
#!/usr/bin/env sh #!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
############################################################################## ##############################################################################
## ##
## Gradle start up script for UN*X ## Gradle start up script for UN*X
@ -44,7 +28,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
@ -82,7 +66,6 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -126,11 +109,10 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath
@ -156,19 +138,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else else
eval `echo args$i`="\"$arg\"" eval `echo args$i`="\"$arg\""
fi fi
i=`expr $i + 1` i=$((i+1))
done done
case $i in case $i in
0) set -- ;; (0) set -- ;;
1) set -- "$args0" ;; (1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;; (2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;; (3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
@ -177,9 +159,14 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }
APP_ARGS=`save "$@"` APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

173
gradlew.bat vendored
View File

@ -1,89 +1,84 @@
@rem @if "%DEBUG%" == "" @echo off
@rem Copyright 2015 the original author or authors. @rem ##########################################################################
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Gradle startup script for Windows
@rem you may not use this file except in compliance with the License. @rem
@rem You may obtain a copy of the License at @rem ##########################################################################
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem Set local scope for the variables with windows NT shell
@rem if "%OS%"=="Windows_NT" setlocal
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, set DIRNAME=%~dp0
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. if "%DIRNAME%" == "" set DIRNAME=.
@rem See the License for the specific language governing permissions and set APP_BASE_NAME=%~n0
@rem limitations under the License. set APP_HOME=%DIRNAME%
@rem
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
@if "%DEBUG%" == "" @echo off set DEFAULT_JVM_OPTS=
@rem ##########################################################################
@rem @rem Find java.exe
@rem Gradle startup script for Windows if defined JAVA_HOME goto findJavaFromJavaHome
@rem
@rem ########################################################################## set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
@rem Set local scope for the variables with windows NT shell if "%ERRORLEVEL%" == "0" goto init
if "%OS%"=="Windows_NT" setlocal
echo.
set DIRNAME=%~dp0 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if "%DIRNAME%" == "" set DIRNAME=. echo.
set APP_BASE_NAME=%~n0 echo Please set the JAVA_HOME variable in your environment to match the
set APP_HOME=%DIRNAME% echo location of your Java installation.
@rem Resolve any "." and ".." in APP_HOME to make it shorter. goto fail
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
:findJavaFromJavaHome
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set JAVA_HOME=%JAVA_HOME:"=%
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set JAVA_EXE=%JAVA_HOME%/bin/java.exe
@rem Find java.exe if exist "%JAVA_EXE%" goto init
if defined JAVA_HOME goto findJavaFromJavaHome
echo.
set JAVA_EXE=java.exe echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
%JAVA_EXE% -version >NUL 2>&1 echo.
if "%ERRORLEVEL%" == "0" goto execute echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. goto fail
echo.
echo Please set the JAVA_HOME variable in your environment to match the :init
echo location of your Java installation. @rem Get command-line arguments, handling Windows variants
goto fail if not "%OS%" == "Windows_NT" goto win9xME_args
:findJavaFromJavaHome :win9xME_args
set JAVA_HOME=%JAVA_HOME:"=% @rem Slurp the command line arguments.
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set CMD_LINE_ARGS=
set _SKIP=2
if exist "%JAVA_EXE%" goto execute
:win9xME_args_slurp
echo. if "x%~1" == "x" goto execute
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. set CMD_LINE_ARGS=%*
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. :execute
@rem Setup the command line
goto fail
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:execute
@rem Setup the command line @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
@rem Execute Gradle if "%ERRORLEVEL%"=="0" goto mainEnd
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:fail
:end rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
@rem End local scope for the variables with windows NT shell rem the _cmd.exe /c_ return code!
if "%ERRORLEVEL%"=="0" goto mainEnd if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of :mainEnd
rem the _cmd.exe /c_ return code! if "%OS%"=="Windows_NT" endlocal
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 :omega
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega