Enhance download page (#277)

This commit is contained in:
Soitora 2020-08-03 02:37:43 +02:00 committed by GitHub
parent 89e5834ebe
commit 74645d7ca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 198 additions and 375 deletions

19
package-lock.json generated
View File

@ -9380,6 +9380,11 @@
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
}, },
"moment": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
},
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -12823,9 +12828,9 @@
} }
}, },
"sweetalert2": { "sweetalert2": {
"version": "9.15.2", "version": "9.17.1",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-9.15.2.tgz", "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-9.17.1.tgz",
"integrity": "sha512-evJfoa49s5ZzSmVc62tslNkzlQoCg2GHjB3MQxawfESppvY5AlXKLGQ3nNKxHXKdodKmIHbkl39DgUhcOK7LgQ==" "integrity": "sha512-D/VE2lT/bKd64/RBglLUtQ+3nsftzjzIiP2iqN6zPzPwf+2djIY+4k8Bg430zxRUn4DkZzyIuU58q3n0J43lvw=="
}, },
"table": { "table": {
"version": "5.4.6", "version": "5.4.6",
@ -13822,6 +13827,14 @@
"resolved": "https://registry.npmjs.org/vue-material-design-icons/-/vue-material-design-icons-4.8.0.tgz", "resolved": "https://registry.npmjs.org/vue-material-design-icons/-/vue-material-design-icons-4.8.0.tgz",
"integrity": "sha512-NNbwK/a14mk92ofBvJa6oBdWi+SO2f27pimoCWziirrbN5Nmt9q0pzELOfvqyy0ncoMJ2BLkd8KfQuXIAhL3Fw==" "integrity": "sha512-NNbwK/a14mk92ofBvJa6oBdWi+SO2f27pimoCWziirrbN5Nmt9q0pzELOfvqyy0ncoMJ2BLkd8KfQuXIAhL3Fw=="
}, },
"vue-moment": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vue-moment/-/vue-moment-4.1.0.tgz",
"integrity": "sha512-Gzisqpg82ItlrUyiD9d0Kfru+JorW2o4mQOH06lEDZNgxci0tv/fua1Hl0bo4DozDV2JK1r52Atn/8QVCu8qQw==",
"requires": {
"moment": "^2.19.2"
}
},
"vue-router": { "vue-router": {
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.3.4.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.3.4.tgz",

View File

@ -38,8 +38,10 @@
"lodash.sortby": "^4.7.0", "lodash.sortby": "^4.7.0",
"marked": "^1.1.1", "marked": "^1.1.1",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"sweetalert2": "^9.17.1",
"vue-agile": "^1.1.3", "vue-agile": "^1.1.3",
"vue-material-design-icons": "^4.8.0", "vue-material-design-icons": "^4.8.0",
"vue-moment": "^4.1.0",
"vue-sweetalert2": "^3.0.6", "vue-sweetalert2": "^3.0.6",
"vuepress-plugin-sitemap": "^2.3.1" "vuepress-plugin-sitemap": "^2.3.1"
} }

View File

@ -1,44 +1,27 @@
<template> <template>
<div class="downloadContainer"> <div id="DownloadButtons">
<button class="downloadStableButton" @click="downloadStable"> <div class="downloadContainer">
{{ downloadStableLabel }} <button id="downloadStable" @click="downloadStable" @keyup.enter="downloadStable">
</button> <CloudDownloadIcon /> Stable
<button class="downloadPreviewButton" @click="downloadPreview"> </button>
{{ downloadPreviewLabel }} <button id="downloadPreview" @click="downloadPreview" @keyup.enter="downloadPreview">
</button> <BugIcon /> Preview
</button>
</div>
<span class="versionNotice">Requires <strong>Android 5.0</strong> or higher.</span>
</div> </div>
</template> </template>
<script> <script>
import axios from "axios"; import axios from "axios";
import CloudDownloadIcon from "vue-material-design-icons/CloudDownload.vue";
import BugIcon from "vue-material-design-icons/Bug.vue";
import { GITHUB_LATEST_API, GITHUB_LATEST_RELEASE, KANADE_LATEST } from "../constants"; import { GITHUB_LATEST_API, GITHUB_LATEST_RELEASE, KANADE_LATEST } from "../constants";
export default { export default {
props: { components: {
downloadStableTag: { CloudDownloadIcon,
type: String, BugIcon,
required: true,
},
downloadPreviewTag: {
type: String,
required: true,
},
downloadStableLabel: {
type: String,
default: "Stable",
},
downloadPreviewLabel: {
type: String,
default: "Preview",
},
downloadStableUrl: {
type: String,
default: undefined,
},
downloadPreviewUrl: {
type: String,
default: undefined,
},
}, },
data() { data() {
@ -50,10 +33,8 @@ export default {
async mounted() { async mounted() {
const { data } = await axios.get(GITHUB_LATEST_API); const { data } = await axios.get(GITHUB_LATEST_API);
// Maybe eventually some release has more than the apk in assets.
const apkAsset = data.assets.find((a) => a.name.includes(".apk")); const apkAsset = data.assets.find((a) => a.name.includes(".apk"));
// Set the values. this.$data.tagName = data.tag_name.slice(1);
this.$data.tagName = data.tag_name;
this.$data.browserDownloadUrl = apkAsset.browser_download_url; this.$data.browserDownloadUrl = apkAsset.browser_download_url;
}, },
@ -61,96 +42,102 @@ export default {
downloadStable() { downloadStable() {
this.$swal({ this.$swal({
title: "Downloading", title: "Downloading",
text: `${this.downloadStableLabel} version is being downloaded.`, html: `Started downloading <strong>Tachiyomi Stable</strong>`,
icon: "success", icon: "success",
focusConfirm: false, focusConfirm: false,
focusCancel: false, focusCancel: false,
timer: 5000, timer: 3000,
timerProgressBar: true, timerProgressBar: true,
customClass: {
confirmButton: "download-confirm-button",
container: "download-container",
},
showClass: { showClass: {
popup: "animated pulse faster", popup: "animate__animated animate__faster animate__pulse",
}, },
hideClass: { hideClass: {
popup: "animated zoomOut faster", popup: "animate__animated animate__faster animate__zoomOut",
}, },
}); });
window.location.assign( window.location.assign(this.$data.browserDownloadUrl || GITHUB_LATEST_RELEASE);
this.$props.downloadStableUrl || window.ga("send", "event", "Action", "Download", "Tachiyomi");
this.$data.browserDownloadUrl ||
GITHUB_LATEST_RELEASE
);
window.ga(
"send",
"event",
"Action",
"Download",
this.downloadStableTag
);
}, },
downloadPreview() { downloadPreview() {
this.$swal({ this.$swal({
title: "Downloading", icon: "warning",
text: `${this.downloadPreviewLabel} version is being downloaded.`, title: "Are you sure?",
icon: "success", html:
focusConfirm: false, "<strong>Tachiyomi Preview</strong> is not recommended if you're not willing to test for and endure issues.",
focusCancel: false, confirmButtonText: "I am sure.",
timer: 5000, showCloseButton: true,
timerProgressBar: true, showCancelButton: false,
customClass: {
confirmButton: "download-confirm-button",
container: "download-container",
},
showClass: { showClass: {
popup: "animated pulse faster", popup: "animate__animated animate__headShake",
}, },
hideClass: { hideClass: {
popup: "animated zoomOut faster", popup: "animate__animated animate__faster animate__zoomOut",
}, },
// eslint-disable-next-line no-shadow
}).then((result) => {
if (result.value) {
this.$swal({
icon: "success",
title: "Downloading",
html: `Started downloading <strong>Tachiyomi Preview</strong>`,
confirmButtonText: "Dismiss",
showCloseButton: false,
showCancelButton: false,
timer: 3000,
timerProgressBar: true,
showClass: {
popup: "animate__animated animate__faster animate__pulse",
},
hideClass: {
popup: "animate__animated animate__faster animate__zoomOut",
},
});
window.location.assign(KANADE_LATEST);
window.ga("send", "event", "Action", "Download", "Tachiyomi Preview");
}
}); });
window.location.assign(
this.$props.downloadPreviewUrl || KANADE_LATEST
);
window.ga(
"send",
"event",
"Action",
"Download",
this.downloadPreviewTag
);
}, },
}, },
}; };
</script> </script>
<style lang="stylus"> <style lang="stylus">
.downloadContainer #DownloadButtons
user-select none
text-align center text-align center
margin 0.3125rem button
.downloadStableButton display inline-block
.downloadPreviewButton margin 0.5em 0
border-style none padding 1em 1em
padding 0.625 2em width 9em
margin 0.3125rem background $accentColor
border-radius $buttonBorderRadius border none
font-family $buttonFontFamily border-radius 4px
font-size 1.125em color #fff
color white font-family inherit
height 3rem font-size 1em
width 8.5rem font-weight 400
&:focus letter-spacing 0.02em
outline none line-height 1
outline-style solid transition background-color .1s ease
text-decoration none
text-transform uppercase
cursor pointer
&:hover &:hover
cursor pointer background darken($accentColor, 10%)
text-decoration none !important &:focus
background-image linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)) box-shadow 0 0 30px #b1aeae52, 0 0 0 1px #fff, 0 0 0 3px rgba(50, 100, 150, 0.4)
.downloadStableButton outline none
background-color $accentColor .downloadContainer
.downloadPreviewButton user-select none
background-color $accentColorSecondary #download
&Stable
background-color $accentColor
&:hover
background-color lighten($accentColor, 10%)
&Preview
background-color $accentColorSecondary
&:hover
background-color lighten($accentColorSecondary, 10%)
.versionNotice
font-size 0.9rem
</style> </style>

View File

@ -0,0 +1,31 @@
<template>
<div class="buildTime">
<span :title="releasePublishExact">{{ releasePublishRelative }}</span>
</div>
</template>
<script>
import axios from "axios";
import { GITHUB_LATEST_API } from "../constants";
export default {
data() {
return {
releasePublishRelative: "at an unknown time",
releasePublishExact: "Can't connect to GitHub.",
};
},
async mounted() {
const { data } = await axios.get(GITHUB_LATEST_API);
this.$data.releasePublishRelative = this.$moment(data.published_at).fromNow();
this.$data.releasePublishExact = this.$moment(data.published_at).toString();
},
};
</script>
<style lang="stylus" scoped>
.buildTime
font-weight 500
display inline-block
</style>

View File

@ -1,29 +0,0 @@
<template>
<div v-html="releaseNotes" class="releaseNotes"></div>
</template>
<script>
import axios from "axios";
import marked from "marked";
import { GITHUB_LATEST_API } from "../constants";
export default {
data() {
return {
releaseNotes: "",
};
},
async mounted() {
const { data } = await axios.get(GITHUB_LATEST_API);
this.$data.releaseNotes = marked(data.body);
},
};
</script>
<style lang="stylus">
.releaseNotes
white-space
h3
font-size 1.1rem
</style>

View File

@ -1,12 +1,10 @@
<template> <template>
<span v-if="fileName" class="fileNameContainer" title="File name"> <span v-if="fileName" class="fileNameContainer" title="File name">
<MaterialIcon class="fileNameIcon" icon-name="get_app" /> <MaterialIcon class="fileNameIcon" icon-name="get_app" />
<span class="fileName">tachiyomi-{{ this.$data.tagName }}.apk</span> <span class="fileName">tachiyomi-v{{ this.$data.tagName }}.apk</span>
<slot /> <slot />
</span> </span>
<span v-else class="downloadTag"> <span v-else class="downloadTag">{{ this.$data.tagName }}</span>
{{ this.$data.tagName }}
</span>
</template> </template>
<script> <script>
@ -22,14 +20,13 @@ export default {
data() { data() {
return { return {
tagName: "", tagName: "0.0.0",
}; };
}, },
async mounted() { async mounted() {
const { data } = await axios.get(GITHUB_LATEST_API); const { data } = await axios.get(GITHUB_LATEST_API);
// Set the values. this.$data.tagName = data.tag_name.slice(1);
this.$data.tagName = data.tag_name;
}, },
}; };
</script> </script>

View File

@ -0,0 +1,41 @@
<template>
<div class="guide whatsNew">
<p class="title">What's new</p>
<div v-html="whatsNew"></div>
<div class="custom-block aside">
<p>
View the full release
<a href="https://github.com/inorichi/tachiyomi/releases/latest" target="_blank" rel="noopener">here</a>
</p>
</div>
</div>
</template>
<script>
import axios from "axios";
import marked from "marked";
import { GITHUB_LATEST_API } from "../constants";
export default {
data() {
return {
whatsNew: "Failed to load data from GitHub.",
};
},
async mounted() {
const { data } = await axios.get(GITHUB_LATEST_API);
this.$data.whatsNew = marked(data.body);
},
};
</script>
<style lang="stylus">
.whatsNew
.title
text-align center
div
white-space
h3
font-size 1.1rem
</style>

View File

@ -5,6 +5,7 @@ import "vue-material-design-icons/styles.css";
import { VueAgile } from "vue-agile"; import { VueAgile } from "vue-agile";
import VueSweetalert2 from "vue-sweetalert2"; import VueSweetalert2 from "vue-sweetalert2";
import VueMoment from "vue-moment";
export default ({ export default ({
Vue, // the version of Vue being used in the VuePress app Vue, // the version of Vue being used in the VuePress app
@ -15,4 +16,5 @@ export default ({
// eslint-disable-next-line vue/match-component-file-name // eslint-disable-next-line vue/match-component-file-name
Vue.component("Agile", VueAgile); Vue.component("Agile", VueAgile);
Vue.use(VueSweetalert2); Vue.use(VueSweetalert2);
Vue.use(VueMoment);
}; };

View File

@ -176,4 +176,4 @@ font-feature-settings()
// Hotfix Mobile Dropdown // Hotfix Mobile Dropdown
.sidebar .dropdown-wrapper .dropdown-title .sidebar .dropdown-wrapper .dropdown-title
pointer-events auto pointer-events auto

View File

@ -1,21 +1,14 @@
<template> <template>
<main class="home" aria-labelledby="main-title"> <main class="home" aria-labelledby="main-title">
<header class="hero"> <header class="hero">
<img <img v-if="data.heroImage" :src="$withBase(data.heroImage)" :alt="data.heroAlt || 'Logo'" />
v-if="data.heroImage"
:src="$withBase(data.heroImage)"
:alt="data.heroAlt || 'Logo'"
/>
<h1 v-if="data.heroText !== null" id="main-title"> <h1 v-if="data.heroText !== null" id="main-title">
{{ data.heroText || "Tachiyomi" }} {{ data.heroText || "Tachiyomi" }}
</h1> </h1>
<p v-if="data.tagline !== null" class="description"> <p v-if="data.tagline !== null" class="description">
{{ {{ data.tagline || "Free and open source manga reader for Android" }}
data.tagline ||
"Free and open source manga reader for Android"
}}
</p> </p>
<p v-if="data.buttonDownload || data.buttonGuides" class="action"> <p v-if="data.buttonDownload || data.buttonGuides" class="action">
@ -23,8 +16,7 @@
v-if="data.buttonDownload" v-if="data.buttonDownload"
class="action-button action-button__Download" class="action-button action-button__Download"
tabindex="0" tabindex="0"
@click="showDownloads" :href="data.buttonDownloadLink"
@keyup.enter="showDownloads"
> >
<CloudDownloadIcon /> <CloudDownloadIcon />
{{ data.buttonDownload }} {{ data.buttonDownload }}
@ -42,27 +34,16 @@
</header> </header>
<div v-if="data.features && data.features.length" class="features"> <div v-if="data.features && data.features.length" class="features">
<div <div v-for="(feature, index) in data.features" :key="index" class="feature">
v-for="(feature, index) in data.features"
:key="index"
class="feature"
>
<div class="feature__Details"> <div class="feature__Details">
<h2>{{ feature.title }}</h2> <h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p> <p>{{ feature.details }}</p>
</div> </div>
<section class="feature__Animation"> <section class="feature__Animation">
<img <img class="feature__Animation--dark" :src="$withBase('/assets/' + feature.image + '-Dark.png')" />
class="feature__Animation--dark"
:src="
$withBase('/assets/' + feature.image + '-Dark.png')
"
/>
<img <img
class="feature__Animation--light" class="feature__Animation--light"
:src=" :src="$withBase('/assets/' + feature.image + '-Light.png')"
$withBase('/assets/' + feature.image + '-Light.png')
"
/> />
</section> </section>
</div> </div>
@ -82,7 +63,7 @@
import axios from "axios"; import axios from "axios";
import CloudDownloadIcon from "vue-material-design-icons/CloudDownload.vue"; import CloudDownloadIcon from "vue-material-design-icons/CloudDownload.vue";
import BookOpenVariantIcon from "vue-material-design-icons/BookOpenVariant.vue"; import BookOpenVariantIcon from "vue-material-design-icons/BookOpenVariant.vue";
import { GITHUB_LATEST_API, GITHUB_LATEST_RELEASE, KANADE_LATEST } from "../../constants"; import { GITHUB_LATEST_API } from "../../constants";
export default { export default {
name: "Home", name: "Home",
@ -106,6 +87,7 @@ export default {
buttonDownload() { buttonDownload() {
return { return {
link: this.data.buttonDownloadLink,
text: this.data.buttonDownload, text: this.data.buttonDownload,
}; };
}, },
@ -121,155 +103,8 @@ export default {
async mounted() { async mounted() {
const { data } = await axios.get(GITHUB_LATEST_API); const { data } = await axios.get(GITHUB_LATEST_API);
const apkAsset = data.assets.find((a) => a.name.includes(".apk")); const apkAsset = data.assets.find((a) => a.name.includes(".apk"));
this.$data.tagName = data.tag_name;
this.$data.browserDownloadUrl = apkAsset.browser_download_url; this.$data.browserDownloadUrl = apkAsset.browser_download_url;
}, },
methods: {
showDownloads() {
this.$swal({
title: "Get Tachiyomi for Android",
text: "Requires Android 5.0 or newer.",
confirmButtonText: "Download",
confirmButtonAriaLabel: "Download Tachiyomi",
cancelButtonText:
"Living on the edge? Get the <strong>Preview</strong>",
cancelButtonAriaLabel: "Download Preview",
showCloseButton: true,
showCancelButton: true,
focusConfirm: true,
customClass: {
container: "showDownloads",
popup: "showDownloads__popup",
actions: "showDownloads__actions",
title: "showDownloads__title",
content: "showDownloads__content",
confirmButton: "showDownloads__confirmButton",
cancelButton: "showDownloads__cancelButton",
closeButton: "showDownloads__closeButton",
footer: "showDownloads__footer",
},
showClass: {
popup: "animate__animated animate__faster animate__fadeIn",
},
hideClass: {
popup: "animate__animated animate__faster animate__zoomOut",
},
}).then((result) => {
if (result.value) {
this.$swal({
icon: "success",
title: "Downloading",
text: "Tachiyomi",
confirmButtonText: "Dismiss",
showCloseButton: false,
showCancelButton: false,
timer: 50000,
timerProgressBar: true,
customClass: {
container: "showDownloads",
popup: "showDownloads__popup",
actions: "showDownloads__actions",
title: "showDownloads__title",
content: "showDownloads__content",
confirmButton: "showDownloads__confirmButton",
cancelButton: "showDownloads__cancelButton",
closeButton: "showDownloads__closeButton",
footer: "showDownloads__footer",
},
showClass: {
popup:
"animate__animated animate__faster animate__pulse",
},
hideClass: {
popup:
"animate__animated animate__faster animate__zoomOut",
},
});
window.location.assign(
this.$data.browserDownloadUrl || GITHUB_LATEST_RELEASE
);
window.ga(
"send",
"event",
"Action",
"Download",
"Tachiyomi"
);
} else if (result.dismiss === "cancel") {
this.$swal({
icon: "warning",
title: "Are you sure?",
html:
"<strong>Preview</strong> is not recommended if you're not willing to test for and endure issues.",
confirmButtonText: "I am sure.",
showCloseButton: true,
showCancelButton: false,
customClass: {
container: "showDownloads",
popup: "showDownloads__popup",
actions: "showDownloads__actions",
title: "showDownloads__title",
content: "showDownloads__content",
confirmButton: "showDownloads__confirmButton",
cancelButton: "showDownloads__cancelButton",
closeButton: "showDownloads__closeButton",
footer: "showDownloads__footer",
},
showClass: {
popup: "animate__animated animate__headShake",
},
hideClass: {
popup:
"animate__animated animate__faster animate__zoomOut",
},
// eslint-disable-next-line no-shadow
}).then((result) => {
if (result.value) {
this.$swal({
icon: "success",
title: "Downloading",
text: "Tachiyomi Preview",
confirmButtonText: "Dismiss",
showCloseButton: false,
showCancelButton: false,
timer: 5000,
timerProgressBar: true,
customClass: {
container: "showDownloads",
popup: "showDownloads__popup",
actions: "showDownloads__actions",
title: "showDownloads__title",
content: "showDownloads__content",
confirmButton:
"showDownloads__confirmButton",
cancelButton: "showDownloads__cancelButton",
closeButton: "showDownloads__closeButton",
footer: "showDownloads__footer",
},
showClass: {
popup:
"animate__animated animate__faster animate__pulse",
},
hideClass: {
popup:
"animate__animated animate__faster animate__zoomOut",
},
});
window.location.assign(KANADE_LATEST);
window.ga(
"send",
"event",
"Action",
"Download",
"Tachiyomi Preview"
);
}
});
}
});
},
},
}; };
</script> </script>
@ -378,56 +213,6 @@ export default {
text-align center text-align center
color lighten($textColor, 25%) color lighten($textColor, 25%)
.showDownloads
button
user-select none
&__popup
width 30em !important
&__cancelButton
&__confirmButton
font-family $buttonFontFamily
&__confirmButton
width 50% !important
margin-left 25% !important
margin-right 25% !important
margin-bottom 8px !important
&__cancelButton
background none !important
color $textColor !important
font-size 0.8rem !important
padding 2px !important
padding-top 4px !important
border-top 1px solid darken($borderColor, 10%) !important
border-radius 0px !important
strong
color $accentColor
&:hover
cursor pointer
opacity 0.8
&:focus
box-shadow 0 0 30px #b1aeae52, 0 0 0 1px #fff, 0 0 0 3px rgba(50, 100, 150, 0.4)
outline none
&:hover
cursor default
&__closeButton
border-radius 6px
width 1em
height 1em
margin-top 0.2em
margin-right 0.2em
&:focus
box-shadow 0 0 30px #b1aeae52, 0 0 0 1px #fff, 0 0 0 3px rgba(50, 100, 150, 0.4)
outline none
&__title
border-bottom-width 0 !important
font-family $buttonFontFamily !important
font-weight 500
&__content
margin-top -10px
font-size 1rem
&__footer
text-align center
@keyframes fade @keyframes fade
0% 0%
opacity 1 opacity 1

View File

@ -6,6 +6,7 @@ meta:
lang: en-US lang: en-US
heroImage: /icons/logo.svg heroImage: /icons/logo.svg
buttonDownload: Download buttonDownload: Download
buttonDownloadLink: /download/
buttonGuides: User Guide buttonGuides: User Guide
buttonGuidesLink: /help/guides/getting-started buttonGuidesLink: /help/guides/getting-started
features: features:

View File

@ -8,15 +8,8 @@ lang: en-US
--- ---
# Download # Download
Download the latest stable version of **Tachiyomi** (**<VersionTag downloadTag/>**) which released <ReleaseDate />.
Download the latest version of **Tachiyomi** for your Android device. <DownloadButtons />
::: aside-guide <WhatsNew />
Latest release: **<VersionTag/>**
:::
<DownloadButtons downloadStableTag="Tachiyomi" downloadPreviewTag="Tachiyomi Preview"/>
::: guide Release notes
<ReleaseNotes/>
:::