diff --git a/fastlane/metadata/android/be-BY/full_description.txt b/fastlane/metadata/android/be-BY/full_description.txt new file mode 100644 index 000000000..69aa29ff9 --- /dev/null +++ b/fastlane/metadata/android/be-BY/full_description.txt @@ -0,0 +1,16 @@ +Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! + +Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network. + +Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. + +More features: + +• Dark Mode: Read posts in light, dark, or true black mode +• Polls: Ask followers for their opinion and tally the votes +• Explore: Trending hashtags and accounts are a tap away +• Notifications: Get notified about new follows, replies, and reblogs +• Sharing: Post directly to Mastodon from any share sheet in any app +• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time + +Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file diff --git a/fastlane/metadata/android/be-BY/short_description.txt b/fastlane/metadata/android/be-BY/short_description.txt new file mode 100644 index 000000000..8f5a9b847 --- /dev/null +++ b/fastlane/metadata/android/be-BY/short_description.txt @@ -0,0 +1 @@ +Decentralized social network \ No newline at end of file diff --git a/fastlane/metadata/android/be-BY/title.txt b/fastlane/metadata/android/be-BY/title.txt new file mode 100644 index 000000000..8123241a0 --- /dev/null +++ b/fastlane/metadata/android/be-BY/title.txt @@ -0,0 +1 @@ +Mastodon \ No newline at end of file diff --git a/fastlane/metadata/android/cs-CZ/full_description.txt b/fastlane/metadata/android/cs-CZ/full_description.txt index 53f0fe637..7596fd3ad 100644 --- a/fastlane/metadata/android/cs-CZ/full_description.txt +++ b/fastlane/metadata/android/cs-CZ/full_description.txt @@ -1,16 +1,16 @@ -Mastodon je největší decentralizovanou sociální sítí na internetu. Místo jednné webové stránky je to síť pro miliony uživatelů v nezávislých komunitách, kteří mohou všichni vzájemně a bezproblémově komunikovat. Bez ohledu na to, co vás baví, můžete se setkat s vášnivými lidmi, kteří o tom vysílají na Mastodon! +Mastodon je největší decentralizovanou sociální sítí na internetu. Místo jediné webové stránky je to síť pro miliony uživatelů v nezávislých komunitách, ve kterých mohou všichni vzájemně a bezproblémově komunikovat. Bez ohledu na to, co vás baví, můžete se setkat s vášnivými lidmi, kteří o tom přispívají na Mastodon! -Připojte se ke komunitě a vytvořte svůj profil. Najděte a sledujte fascinující lidi a přečtěte si jejich příspěvky v bezreklamní a chronologické časové linii. Vyjádřete se pomocí vlastních emojí, obrázků, GIFů, videí a zvuku v 500-znakových příspěvcích. Odpovězte na vlákna a reblogujte příspěvky od kohokoliv, abyste mohli sdílet skvělé věci. Najděte nové účty pro sledování a populární hashtagy pro rozšíření vaší sítě. +Připojte se ke komunitě a vytvořte svůj profil. Najděte a sledujte fascinující lidi a přečtěte si jejich příspěvky v chronologické časové ose bez reklam. Vyjádřete se pomocí vlastních emoji, obrázků, GIFů, videí a zvuku v 500-znakových příspěvcích. Odpovězte na vlákna a boostujte příspěvky od kohokoliv, abyste mohli sdílet skvělé věci. Najděte nové účty pro sledování a populární hashtagy pro rozšíření vaší sítě. -Mastodon je postaven se zaměřením na soukromí a bezpečnost. Rozhodněte, zda jsou vaše příspěvky sdíleny se svými sledujícími, jen s lidmi, které zmiňujete, nebo s celým světem. Upozornění na obsah vám umožní skrýt příspěvky obsahující citlivý nebo spouštěcí materiál, dokud se s nimi nezačnete zabývat. Každá komunita má vlastní pokyny a moderátory, aby udržela své členy v bezpečí, a robustní blokování a hlášení nástrojů pomáhá předcházet zneužití. +Mastodon je postaven se zaměřením na soukromí a bezpečnost. Rozhodněte, zda jsou vaše příspěvky sdíleny se vašimi sledujícími, jen s lidmi, které zmíníte, nebo s celým světem. Upozornění na obsah vám umožní skrýt příspěvky obsahující citlivý nebo spouštěcí materiál, dokud se s nimi nezačnete zabývat. Každá komunita má vlastní pokyny a moderátory, aby udržela své členy v bezpečí, a robustní blokování a nahlašovací nástroje pomáhácí předcházení zneužití. -Další funkce: +Více funkcí: -• Tmavý režim: Čtěte příspěvky ve světlém, tmavém nebo zcela černém režimu -• Ankety: Požádejte sledující o jejich názor a spojte se s jejich hlasováním -• Průzkum: Trendové hashtagy a účty jsou pryč na jedno klepnutí -• Upozornění: Dostávejte upozornění na nové sledování, odpovědi a reblogy +• Tmavý režim: Čtěte příspěvky ve světlém, tmavém nebo pravém černém režimu +• Ankety: Požádejte sledující o jejich názor a sečtěte jejich hlasy +• Objevit: Populární hashtagy a účty jsou pryč na jedno klepnutí +• Oznámení: Dostávejte oznámení o nových sledujících, odpovědích a boostech • Sdílení: Odesílání přímo do Mastodonu z libovolného seznamu sdílení v jakékoliv aplikaci • Roztomilost: Naším maskotem je roztomilý slon, kterého čas od času uvidíte -Mastodon je registrovaný neziskový projekt a vývojový program je podporován přímo vašimi dary. Neexistuje žádná reklama, žádná monetizace a žádný rizikový kapitál a my máme v plánu to udržet. \ No newline at end of file +Mastodon je registrovaný neziskový projekt a vývojový program je podporován přímo vašimi dary. Neexistuje žádná reklama, žádná monetizace a žádný rizikový kapitál a máme v plánu to udržet. \ No newline at end of file diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt index 612f6b2f6..9d2abfa3b 100644 --- a/fastlane/metadata/android/de-DE/full_description.txt +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -1,16 +1,16 @@ -Mastodon ist das größte dezentralisierte soziale Netzwerk im Internet. Statt einer einzigen Website ist es ein Netzwerk von Millionen von Benutzer*innen in unabhängigen Gemeinschaften, die alle miteinander interagieren können. Egal was dich interessiert, auf Mastodon kannst du interessierte Leute treffen, die darüber schreiben! +Mastodon ist das größte dezentralisierte soziale Netzwerk im Internet. Statt einer einzigen Webseite ist es ein Netzwerk von Millionen von Benutzer*innen in unabhängigen Gemeinschaften, die alle miteinander interagieren können. Egal, was du magst, auf Mastodon kannst du begeisterte Menschen treffen, die darüber schreiben! -Tritt einer Gemeinschaft bei und erstelle dein Profil. Finde und folge faszinierenden Leuten, und lies ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit benutzerdefinierten Emojis, Bildern, GIFs, Videos und Audio in 500-Zeichen-Beiträgen aus. Antworte auf Threads und teile Beiträge von anderen, um großartige Sachen zu verbreiten. Finde neue Accounts zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern. +Tritt einer Gemeinschaft bei und erstelle dein Profil. Finde und folge faszinierenden Leuten und lies ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit eigenen Emojis, Bildern, GIFs, Videos und Klängen in 500-Zeichen-Beiträgen aus. Antworte auf Themen und teile Beiträge von anderen, um tolle Dinge zu verbreiten. Finde neue Konten zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern. -Mastodon wurde mit einem Schwerpunkt auf Privatsphäre und Sicherheit gebaut. Entscheide, ob du deine Beiträge mit deinen Followern, nur mit den Menschen, die du erwähnst, oder mit der ganzen Welt teilen möchtest. Mit Inhaltswarnungen kannst du Beiträge mit sensiblem oder triggerndem Inhalt ausblenden, bis du bereit bist, dich damit auseinanderzusetzen. Jede Gemeinschaft hat ihre eigenen Regeln und Moderator*innen, um die Sicherheit ihrer Mitglieder zu gewährleisten, sowie robuste Sperr- und Meldewerkzeuge, um Missbrauch vorzubeugen. +Mastodon wurde mit einem Schwerpunkt auf Privatsphäre und Sicherheit gebaut. Entscheide, ob du deine Beiträge mit deinen Followern, nur mit den Menschen, die du erwähnst, oder mit der ganzen Welt teilen möchtest. Mit Inhaltswarnungen kannst du Beiträge mit sensiblem oder bedenklichen Inhalten ausblenden, bis du bereit bist, dich damit auseinanderzusetzen. Jede Gemeinschaft hat ihre eigenen Regeln und Moderator*innen, um die Sicherheit ihrer Mitglieder zu gewährleisten, sowie robuste Sperr- und Meldewerkzeuge, um Missbrauch vorzubeugen. Weitere Funktionen: • Dunkler Modus: Beiträge im hellen, dunklen oder schwarzen Modus lesen -• Umfragen: Frage deine Follower nach ihrer Meinung und zähle die Stimmen +• Umfragen: frage deine Follower nach ihrer Meinung und zähle die Stimmen • Entdecken: trendende Hashtags und Profile sind nur einen Fingertipp entfernt -• Benachrichtigungen: Erhalte Benachrichtigungen über neue Follower, Antworten und geteilte Beiträge -• Teilen: Veröffentliche auf Mastodon aus jeder beliebigen anderen App -• Niedlichkeit: Unser Maskottchen ist ein entzückender Elefant, und du wirst ihn von Zeit zu Zeit auftauchen sehen +• Benachrichtigungen: erhalte Benachrichtigungen über neue Follower, Antworten und geteilte Beiträge +• Teilen: veröffentliche auf Mastodon aus jeder beliebigen anderen App +• Niedlichkeit: unser Maskottchen ist ein entzückender Elefant und du wirst ihn von Zeit zu Zeit auftauchen sehen -Mastodon ist eine eingetragene gemeinnützige Organisation, und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetisierung und kein Venture-Capital, und wir planen, das auch so beizubehalten. \ No newline at end of file +Mastodon ist eine eingetragene gemeinnützige Organisation und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetarisierung und kein Risikokapital und so soll es auch bleiben. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 69aa29ff9..11d74abb1 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,6 +1,6 @@ Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! -Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network. +Join a community and create your profile. Find and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network. Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. @@ -13,4 +13,4 @@ More features: • Sharing: Post directly to Mastodon from any share sheet in any app • Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time -Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file +Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. diff --git a/fastlane/metadata/android/fil-PH/full_description.txt b/fastlane/metadata/android/fil-PH/full_description.txt new file mode 100644 index 000000000..69aa29ff9 --- /dev/null +++ b/fastlane/metadata/android/fil-PH/full_description.txt @@ -0,0 +1,16 @@ +Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! + +Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network. + +Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. + +More features: + +• Dark Mode: Read posts in light, dark, or true black mode +• Polls: Ask followers for their opinion and tally the votes +• Explore: Trending hashtags and accounts are a tap away +• Notifications: Get notified about new follows, replies, and reblogs +• Sharing: Post directly to Mastodon from any share sheet in any app +• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time + +Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file diff --git a/fastlane/metadata/android/fil-PH/short_description.txt b/fastlane/metadata/android/fil-PH/short_description.txt new file mode 100644 index 000000000..8f5a9b847 --- /dev/null +++ b/fastlane/metadata/android/fil-PH/short_description.txt @@ -0,0 +1 @@ +Decentralized social network \ No newline at end of file diff --git a/fastlane/metadata/android/fil-PH/title.txt b/fastlane/metadata/android/fil-PH/title.txt new file mode 100644 index 000000000..8123241a0 --- /dev/null +++ b/fastlane/metadata/android/fil-PH/title.txt @@ -0,0 +1 @@ +Mastodon \ No newline at end of file diff --git a/fastlane/metadata/android/ga-IE/full_description.txt b/fastlane/metadata/android/ga-IE/full_description.txt new file mode 100644 index 000000000..69aa29ff9 --- /dev/null +++ b/fastlane/metadata/android/ga-IE/full_description.txt @@ -0,0 +1,16 @@ +Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! + +Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network. + +Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. + +More features: + +• Dark Mode: Read posts in light, dark, or true black mode +• Polls: Ask followers for their opinion and tally the votes +• Explore: Trending hashtags and accounts are a tap away +• Notifications: Get notified about new follows, replies, and reblogs +• Sharing: Post directly to Mastodon from any share sheet in any app +• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time + +Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file diff --git a/fastlane/metadata/android/ga-IE/short_description.txt b/fastlane/metadata/android/ga-IE/short_description.txt new file mode 100644 index 000000000..8f5a9b847 --- /dev/null +++ b/fastlane/metadata/android/ga-IE/short_description.txt @@ -0,0 +1 @@ +Decentralized social network \ No newline at end of file diff --git a/fastlane/metadata/android/ga-IE/title.txt b/fastlane/metadata/android/ga-IE/title.txt new file mode 100644 index 000000000..8123241a0 --- /dev/null +++ b/fastlane/metadata/android/ga-IE/title.txt @@ -0,0 +1 @@ +Mastodon \ No newline at end of file diff --git a/fastlane/metadata/android/gd-GB/full_description.txt b/fastlane/metadata/android/gd-GB/full_description.txt index 69aa29ff9..67f644108 100644 --- a/fastlane/metadata/android/gd-GB/full_description.txt +++ b/fastlane/metadata/android/gd-GB/full_description.txt @@ -1,16 +1,16 @@ -Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! +’S e an lìonra sòisealta sgaoilte as motha air an eadar-lìon a th’ ann am Mastodon. Seach aon làrach-lìn a-mhàin, ’s e lìonra de mhilleanan de dhaoine ann an coimhearsnachdan neo-eisimeileach a th’ ann agus ’s urrainn dhan a h-uile duine bruidhinn ri chèile fhathast gun duilgheadas. Ge b’ e dè na rudan a tha ùidh agad annta, coinnichidh tu ri daoine a sgrìobhas mun dèidhinn air Mastodon! -Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network. +Faigh ballrachd ann an coimhearsnachd ’s cruthaich pròifil dhut. Lorg is lean daoine inntinneach agus leugh na postaichean aca air loidhne-ama cheart gun sanasachd. Cuir thu fhèin an cèill le Emojis gnàthaichte, dealbhan, GIFs, videothan is fuaimean ann am postaichean le 500 caractar. Freagair ri snàithleanan is brosnaich postaichean le neach sam bith airson deagh rudan a cho-roinneadh. Lorg cunntasan ùra ri leantainn is tagaichean hais a’ treandadh airson an lìonra agad a leudachadh. -Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. +Chaidh Mastodon a thogail leis an aire air prìobhaideachd is sàbhailteachd. Tha e an urra riut fhèin an co-roinn thu post leis an luchd-leantainn agad, leis na daoine air an doir thu iomradh a-mhàin no leis an t-saoghal mhòr. Leigidh rabhaidhean susbainte leat postaichean sa bheil susbaint fhrionasach fhalach is cha leig daoine leas coimhead air ach nuair a bhios iad deònach. Tha riaghailtean is maoir fa leth aig gach coimhearsnachd airson a buill a chumail sàbhailte agus cuidichidh innealan bacaidh is gearain le dìon o dhroch-dhìol. -More features: +Gleusan eile: -• Dark Mode: Read posts in light, dark, or true black mode -• Polls: Ask followers for their opinion and tally the votes -• Explore: Trending hashtags and accounts are a tap away -• Notifications: Get notified about new follows, replies, and reblogs -• Sharing: Post directly to Mastodon from any share sheet in any app -• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time +• Modh dorcha: Leugh postaichean le modh soilleir, dorcha no dubh dorcha +• Cunntasan-bheachd: Faighnich dhen luchd-leantainn dè am beachd is faigh cunntas nam bhòt +• Rùraich: Ruig tagaichean hais is cunntasan a’ treandadh le aon ghnogag +• Brathan: Faigh brathan mu luchd-leantainn, freagairtean is brosnachaidhean ùra +• Co-roinn: Postaich gu Mastodon gu dìreach o shiota co-roinnidh ann an aplacaid sam bith +• Stampachd: ’S e ailbhean ealanta a tha san t-suaichnean againn is nochdaidh e o àm gu àm -Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file +’S e bhuidheann neo-phrothaideach clàraichte a th’ ann am Mastodon a gheibh taic dhìreach o na tabhartasan agad. Chan eil sanasachd, airgeadachadh no calpa iomairte sam bith ann agus tha fainear dhuinn ’ga chumail mar sin. \ No newline at end of file diff --git a/fastlane/metadata/android/gd-GB/short_description.txt b/fastlane/metadata/android/gd-GB/short_description.txt index 8f5a9b847..9989353d8 100644 --- a/fastlane/metadata/android/gd-GB/short_description.txt +++ b/fastlane/metadata/android/gd-GB/short_description.txt @@ -1 +1 @@ -Decentralized social network \ No newline at end of file +Lìonra sòisealta sgaoilte \ No newline at end of file diff --git a/fastlane/metadata/android/hu-HU/full_description.txt b/fastlane/metadata/android/hu-HU/full_description.txt new file mode 100644 index 000000000..8e76178ab --- /dev/null +++ b/fastlane/metadata/android/hu-HU/full_description.txt @@ -0,0 +1,16 @@ +A Mastodon a legnagyobb decentralizált közösségi hálózat az interneten. Egyetlen weboldal helyett, ez több millió felhasználóból álló, független közösségek hálózata, amelyek egymással kapcsolatba tudnak lépni, zökkenőmentesen. Nem számít, mi a hobbid, a Mastodonon találkozhatsz róla posztoló lelkes emberekkel! + +Csatlakozz egy közösséghez és készítsd el a profilodat. Keress és kövess lenyűgöző embereket, és olvasd egy reklámmentes, kronologikus idővonalon a bejegyzéseiket. Fejezd ki magad egyedi hangulatjelekkel, képekkel, GIFekkel, videókkal és hanggal, 500 karakter hosszúságú posztokban. Reply to threads and reblog posts from anyone to share great stuff. Fedezz fel új fiókokat amiket követhetsz és felkapott hashtageket, hogy bővíthesd a kapcsolataidat. + +A Mastodon az adatvédelemre és a biztonságra összpontosítva épült. Döntsd el, hogy a posztjaidat csak a követőiddel, csak azokkal akiket megemlítesz, vagy az egész világgal osztod meg. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. + +More features: + +• Dark Mode: Read posts in light, dark, or true black mode +• Polls: Ask followers for their opinion and tally the votes +• Explore: Trending hashtags and accounts are a tap away +• Notifications: Get notified about new follows, replies, and reblogs +• Sharing: Post directly to Mastodon from any share sheet in any app +• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time + +Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file diff --git a/fastlane/metadata/android/hu-HU/short_description.txt b/fastlane/metadata/android/hu-HU/short_description.txt new file mode 100644 index 000000000..bf21b3e31 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/short_description.txt @@ -0,0 +1 @@ +Decentralizált szociális hálózat \ No newline at end of file diff --git a/fastlane/metadata/android/hu-HU/title.txt b/fastlane/metadata/android/hu-HU/title.txt new file mode 100644 index 000000000..8123241a0 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/title.txt @@ -0,0 +1 @@ +Mastodon \ No newline at end of file diff --git a/fastlane/metadata/android/id-ID/full_description.txt b/fastlane/metadata/android/id-ID/full_description.txt index 93bd490d3..382a041b2 100644 --- a/fastlane/metadata/android/id-ID/full_description.txt +++ b/fastlane/metadata/android/id-ID/full_description.txt @@ -1,16 +1,16 @@ -Mastodon adalah jejaring sosial terdesentralisasi terbesar di internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! +Mastodon adalah jejaring sosial terdesentralisasi terbesar di internet. Daripada sebuah satu situs web, ini adalah jaringan dari jutaan pengguna dalam komunitas tersendiri yang dapat berinteraksi antar sesama, tanpa masalah. Tanpa memedulikan apa yang Anda minat, Anda dapat bertemu orang-orang yang mengirimkan apa yang mereka minat di Mastodon! -Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Temukan akun-akun baru untuk diikuti dan hashtag yang sedang tren untuk memperluas jejaring Anda. +Bergabung sebuah komunitas dan buat profil Anda. Temukan dan ikuti orang-orang menarik dan lihat kiriman mereka dalam linimasa kronologis tanpa iklan. Ekspresikan diri Anda dengan emoji kustom, gambar, GIF, video, dan audio dalam kiriman dengan batasan 500 karakter. Balas ke utasan dan bagikan kiriman dari siapa pun ke pengikut Anda untuk membagikan hal-hal yang keren. Temukan akun baru untuk diikuti dan tagar yang sedang tren untuk memperluas jejaring Anda. -Mastodon dibuat dengan fokus pada privasi dan keamanan. Tentukan apakah postingan Anda dibagikan kepada pengikut, hanya orang yang disebut, atau seluruh dunia. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. +Mastodon dibuat dengan fokus pada privasi dan keamanan. Tentukan apakah kiriman Anda dibagikan kepada pengikut, hanya orang yang disebut, atau seluruh dunia. Peringatan konten memungkinkan Anda untuk menyembunyikan kiriman yang berisi material sensitif atau memicu sampai Anda siap untuk terlibat dengan mereka. Setiap komunitas memiliki pedoman dan moderator sendiri-sendiri untuk menjaga anggotanya aman, dan alat pemblokiran dan pelaporan yang kokoh membantu mencegah pelecehan. Fitur lainnya: -• Dark Mode: Read posts in light, dark, or true black mode -• Polls: Ask followers for their opinion and tally the votes -• Explore: Trending hashtags and accounts are a tap away -• Notifications: Get notified about new follows, replies, and reblogs -• Sharing: Post directly to Mastodon from any share sheet in any app -• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time +• Mode Gelap: Baca kiriman dalam mode terang, gelap, atau gelap asli +• Pemungutan suara: Tanya pengikut tentang opini mereka dan hitung pilihannya +• Jelajahi: Tagar dan akun tren dengan satu ketuk +• Pemberitahuan: Dapatkan pemberitahuan tentang pengikut, balasan, dan pembagian baru +• Pembagian: Kirim langsung ke Mastodon dari lembar pembagian apa pun dalam aplikasi apa pun +• Kelucuan: Maskot kami adalah seekor gajah yang lucu, dan Anda akan melihat dia muncul dari waktu ke waktu -Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file +Mastodon adalah nirlaba yang terdaftar dan pengembangan didukung secara langsung dari donasi Anda. Tanpa periklanan, tanpa monetisasi, dan tanpa kapitalisme ventura, dan kami berencana untuk tetap seperti itu. \ No newline at end of file diff --git a/fastlane/metadata/android/is-IS/full_description.txt b/fastlane/metadata/android/is-IS/full_description.txt new file mode 100644 index 000000000..556b860f3 --- /dev/null +++ b/fastlane/metadata/android/is-IS/full_description.txt @@ -0,0 +1,30 @@ +Mastodon er stærsta ómiðstýrða samfélagsnetið á internetinu. Í staðinn fyrir að vera á inu vefsvæði, er þetta net með milljónum notenda í +sjálfstæðum samfélögum, sem geta óhindrað átt í samskiptum við hvern annan. Sama hvað þú ert að pæla, alltaf geturðu hitt áhugasamt fólk í gegnum +færslur á Mastodon! + +Taktu þátt í samfélagi og útbúðu notandasnið fyrir þig. Finndu og fylgstu með áhugaverðu fólki og lestu færslurnar þeirra á +auglýsingalausri, raðaðri tímalínu. Tjáðu þig með sérsniðnum emoji-táknum, myndum, GIF-hreyfimyndum, myndskeiðum +og hljóðskrám í 500-stafa færslum. Svaraðu spjallþráðum og endurbirtu færslur frá hverjum sem er til að deila +frábæru efni. Finndu nýja notendur til að fylgjast með og skoðaðu vinsæl myllumerki til að +útvíkka netið þitt. + +Mastodon er byggt með áherslu á gagnaleynd og öryggi. Ákveddu hvort færslunum þínum sé deilt með þeim sem fylgjast með þér, aðeins +fólkinu sem þú minnist á, eða allri veröldinni. Viðvaranir vegna efnis gera þér kleift að fela færslur sem innihalda +viðkvæmt eða eldfimt efni þangað til þú ert í stuði til að eiga við slíkt. Hvert samfélag er með sínar eigin reglur og umsjónarmenn til að passa upp á +öryggi meðlimanna, auk áreiðanlegra verkfæra til að útiloka aðila og +meðhöndla kærur, sem hjálpar til við að koma í veg fyrir misnotkun. + +Fleiri eiginleikar: + +• Dökkur hamur: Lestu færslur í ljósum, dökkum eða sönnum kolsvörtum ham +• Kannanir: Spyrðu fylgjendur um skoðanir þeirra og teldu atkvæðin +• Uppgötva: Vinsæl myllumerki og notendaaðgangar eru við hendina +• Tilkynningar: Fáðu tilkynningar um nýja fylgjendur, svör og endurbirtingar +• Deiling: Birtu beint á Mastodon frá hvaða deilingarblaði sem er í hvaða +forriti sem er +• Krúttlegheit: Gæludýrið okkar er vinalegur loðfíll sem þú gætir rekist á +öðru hverju + +Mastodon er skráð óhagnaðardrifin sjálfseignarstofnun og er þróun þess +drifin áfram með styrkjum frá þér. Það eru engar auglýsingar, engin gjaldtaka og engir áhættufjárfestar - við +höfum hugsað okkur að halda því þannig. \ No newline at end of file diff --git a/fastlane/metadata/android/is-IS/short_description.txt b/fastlane/metadata/android/is-IS/short_description.txt new file mode 100644 index 000000000..60c4e2e6b --- /dev/null +++ b/fastlane/metadata/android/is-IS/short_description.txt @@ -0,0 +1 @@ +Dreifstýrt samfélagsnet \ No newline at end of file diff --git a/fastlane/metadata/android/is-IS/title.txt b/fastlane/metadata/android/is-IS/title.txt new file mode 100644 index 000000000..8123241a0 --- /dev/null +++ b/fastlane/metadata/android/is-IS/title.txt @@ -0,0 +1 @@ +Mastodon \ No newline at end of file diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt index 7cd0be6ea..4bb23bf3a 100644 --- a/fastlane/metadata/android/nl-NL/full_description.txt +++ b/fastlane/metadata/android/nl-NL/full_description.txt @@ -1,16 +1,16 @@ -Mastodon is het grootste gedecentraliseerde sociale netwerk op het internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! +Mastodon is het grootste gedecentraliseerde sociale netwerk op het internet. In plaats van één enkele website is het een netwerk van miljoenen gebruikers in onafhankelijke gemeenschappen die allemaal naadloos met elkaar kunnen communiceren. Waar je ook mee bezig bent, je kunt gepassioneerde mensen ontmoeten die erover berichten op Mastodon! -Word lid van een community en maak je profiel aan. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Antwoord op berichten en boost iedereens berichten om geweldige dingen te delen. Find new accounts to follow and trending hashtags to expand your network. +Word lid van een gemeenschap en maak je profiel aan. Vind en volg fascinerende mensen en lees hun berichten in een advertentievrije, chronologische tijdlijn. Druk jezelf uit met aangepaste emoji, afbeeldingen, GIF’s, video’s en audio in berichten van 500 karakters. Antwoord op berichten en boost iedereens berichten om geweldige dingen te delen. Vind nieuwe accounts om te volgen en hashtags om je netwerk uit te breiden. -Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. +Mastodon is gebouwd met een focus op privacy en veiligheid. Bepaal zelf of je berichten met je volgers, alleen de mensen die je noemt, of de hele wereld worden gedeeld. Inhoudswaarschuwingen laten je berichten verbergen die gevoelig of aanmatigend materiaal bevatten, totdat je er klaar voor bent om ermee ze te bekijken. Elke gemeenschap heeft haar eigen richtlijnen en moderators om haar leden veilig te houden, en robuuste blokkerings- en rapportagetools helpen misbruik te voorkomen. Meer mogelijkheden: -• Dark Mode: Read posts in light, dark, or true black mode -• Polls: Ask followers for their opinion and tally the votes -• Explore: Trending hashtags and accounts are a tap away +• Donkere Modus: Berichten lezen in licht, donker of echt zwart +• Polls: Vraag volgers om hun mening en tel de stemmen +• Ontdekken: Trending hashtags en accounts zijn een tik weg • Meldingen: Krijg een melding over nieuwe volgers, reacties en boosts -• Sharing: Post directly to Mastodon from any share sheet in any app -• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time +• Delen: Deel vanuit elke app direct op Mastodon +• Schattigheid: Onze mascotte is een schattige olifant, en je zult ze van tijd tot tijd zien verschijnen -Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file +Mastodon is een geregistreerde non-profit en de ontwikkeling wordt direct ondersteund door jouw donaties. Er is geen reclame, geen geldelijk gewin en geen durfkapitaal en we zijn van plan het zo te houden. \ No newline at end of file diff --git a/fastlane/metadata/android/ro-RO/full_description.txt b/fastlane/metadata/android/ro-RO/full_description.txt new file mode 100644 index 000000000..ee054e57a --- /dev/null +++ b/fastlane/metadata/android/ro-RO/full_description.txt @@ -0,0 +1,16 @@ +Mastodon este cea mai mare rețea socială descentralizată de pe internet. În loc de un singur site, este o rețea de milioane de utilizatori din comunități independente care pot interacționa cu ceilalți, fără nici o întrerupere. Indiferent în ce te afli, poți întâlni oameni pasionați care postează despre asta pe Mastodon! + +Alătură-te unei comunități și creează-ți profilul. Găsește și urmărește oameni fascinanți și citește postările lor într-un calendar cronologic fără reclame. Exprimă-te cu emoji-uri personalizate, imagini, GIF-uri, videoclipuri și audio în postări de 500 de caractere. Răspunde la subiectele de discuție și impulsionează postările de la oricine pentru a împărtăși lucruri minunate. Găsește conturi noi de urmărit și haștag-uri populare pentru a-ți extinde rețeaua. + +Mastodon a fost construit cu accent pe confidențialitate și siguranță. Decide dacă postările tale sunt partajate cu urmăritorii tăi, doar cu cei pe care îi menționezi sau cu întreaga lume. Avertismentele de conținut vă permit să ascundeți postările care conțin materiale sensibile sau declanșatoare până când sunteți gata să le implicați. Fiecare comunitate are propriile sale orientări și proprii moderatori pentru a-și menține membrii în siguranță, iar instrumentele solide de blocare și raportare contribuie la prevenirea abuzurilor. + +Mai multe caracteristici: + +• Mod întunecat: Citește postările în modul luminos, întunecat sau negru total +• Sondaje: Cereți celor care vă urmăresc opinia lor și numărați voturile +• Explorează: Hașhtag-urile populare și conturile sunt la o apăsare distanță +• Notificări: Primiți notificări despre noi urmăritori, răspunsuri și impulsionări +• Distribuire: Postează direct pe Mastodon din orice foaie de partajare în orice aplicație +• Drăgălășenie: Mascota noastră este un elefant adorabil, și îi veți vedea apărând din când în când + +Mastodon este o organizație non-profit înregistrată, iar dezvoltarea este sprijinită direct de donațiile tale. Nu există publicitate, monetizare și capital de risc, și intenționăm să păstrăm lucrurile astfel. \ No newline at end of file diff --git a/fastlane/metadata/android/ro-RO/short_description.txt b/fastlane/metadata/android/ro-RO/short_description.txt new file mode 100644 index 000000000..d8216713b --- /dev/null +++ b/fastlane/metadata/android/ro-RO/short_description.txt @@ -0,0 +1 @@ +Rețea socială descentralizată \ No newline at end of file diff --git a/fastlane/metadata/android/ro-RO/title.txt b/fastlane/metadata/android/ro-RO/title.txt new file mode 100644 index 000000000..8123241a0 --- /dev/null +++ b/fastlane/metadata/android/ro-RO/title.txt @@ -0,0 +1 @@ +Mastodon \ No newline at end of file diff --git a/fastlane/metadata/android/sl-SI/full_description.txt b/fastlane/metadata/android/sl-SI/full_description.txt new file mode 100644 index 000000000..ccdf79548 --- /dev/null +++ b/fastlane/metadata/android/sl-SI/full_description.txt @@ -0,0 +1,16 @@ +Mastodon je največje decentralizirano družbeno omrežje na internetu. Namesto enega samega spletišča ga tvorijo milijoni uporabnikov v neodvisnih skupnostih, ki lahko med seboj komunicirajo brez težav. Ne glede na to, kaj vas zanima, lahko srečate predane ljudi, ki o tem objavljajo na Mastodonu! + +Pridružite se skupnosti in ustvarite svoj profil. Poiščite in sledite zanimivim osebam ter berite njihove objave na časovnici brez oglasov v kronološkem zaporedju. Izrazite se s čustvenčki po meri, slikami, GIF-i, videoposnetki in zvočnimi posnetki v objavah z največ 500 znaki. Odgovarjajte na niti in poobjavite objave drugih, da delite dobro z drugimi. Poiščite nove račune za sledenje ter ključnike v trendu, da razširite svoje omrežje. + +Mastodon je izdelan s poudarkom na zasebnosti in varnosti. Odločite se, ali se vaše objave delijo z vašimi sledilci, zgolj z omenjenimi ali s celim svetom. Opozorila o vsebini omogočajo skrivanje objav, ki vsebujejo občutljive ali netilne zadeve, vse dokler niste pripravljeni, da se z njimi spopadete. Vsak skupnost ima svoja lastna pravila in moderatorje, ki varujejo svoje člane, ter robustna orodja za blokiranje in poročanje, ki pomagajo preprečiti žalitve in kršitve človeškega dostojanstva ter pravic. + +Dodatne funkcionalnosti: + +• Temni način: objave berite v svetlem, temnem ali povsem črnem načinu; +• Ankete: vprašajte sledilce o njihovem mnenju in preštejte njihove glasove; +• Razišči: ključniki in računi v trendu so le en tap stran; +• Obvestila: bodite obveščeni o novih sledenjih, odgovorih in poobjavah; +• Skupna raba: objavljajte neposredno v Mastodon s poljubne preglednice v skupni rabi; +• Srčkano: naša maskota je ljubek slon in videli boste, kako se sem ter tja pojavi. + +Mastodon je registrirana neprofitna organizacija, razvoj pa podpirajo neposredno vaše donacije. Je brez oglaševanja, monetizacije in brez rizičnega kapitala; nameravamo ga takšnega tudi obdržati. \ No newline at end of file diff --git a/fastlane/metadata/android/sl-SI/short_description.txt b/fastlane/metadata/android/sl-SI/short_description.txt new file mode 100644 index 000000000..d5c29d5d8 --- /dev/null +++ b/fastlane/metadata/android/sl-SI/short_description.txt @@ -0,0 +1 @@ +Decentralizirano družbeno omrežje \ No newline at end of file diff --git a/fastlane/metadata/android/sl-SI/title.txt b/fastlane/metadata/android/sl-SI/title.txt new file mode 100644 index 000000000..8123241a0 --- /dev/null +++ b/fastlane/metadata/android/sl-SI/title.txt @@ -0,0 +1 @@ +Mastodon \ No newline at end of file diff --git a/fastlane/metadata/android/uk-UA/full_description.txt b/fastlane/metadata/android/uk-UA/full_description.txt index 69aa29ff9..12bf44f66 100644 --- a/fastlane/metadata/android/uk-UA/full_description.txt +++ b/fastlane/metadata/android/uk-UA/full_description.txt @@ -1,16 +1,16 @@ -Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon! +Mastodon — найбільша децентралізована соціальна мережа в інтернеті. Замість одного сайту це мережа мільйонів користувачів у незалежних спільнотах, які можуть взаємодіяти один з одним. Незалежно від того, чим ви займаєтеся, ви можете зустріти захоплених людей, які пишуть про це на Mastodon! -Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network. +Приєднуйтесь до спільноти і створіть свій профіль. Знайдіть і підпишіться на цікавих людей і читайте пости у вільний від реклами стрічці. Виразіть себе за допомогою користувацьких емоджі, зображень, GIF, відео й аудіо з 500-символьними постами. Відповідайте на теми й робіть репости постів від будь-кого, щоб ділитися з ними гарними матеріалами. Знаходьте нові облікові записи, щоб підписатися і популярні хештеги для розширення вашої мережі. -Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse. +Mastodon будується з акцентом на конфіденційність та безпеці. Вирішіть, чи будуть ваші пости тільки для підписників, або ті люди, з яких ви згадали, чи цілий світ. Попередження щодо вмісту дозволяють приховати публікації, що містять конфіденційний або провокаційний матеріал, доки ви не будете готові до нього. Кожна спільнота має свої правила і модераторів, щоб залишити учасників в безпеці, а також надійне блокування та інструменти для скарг, щоб запобігти зловживання. -More features: +Більше можливостей: -• Dark Mode: Read posts in light, dark, or true black mode -• Polls: Ask followers for their opinion and tally the votes -• Explore: Trending hashtags and accounts are a tap away -• Notifications: Get notified about new follows, replies, and reblogs -• Sharing: Post directly to Mastodon from any share sheet in any app -• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time +• Темна Тема: Читайте у світлій, темній, або справжній чорній темі +• Опитування: запитуйте думку підписникіна та підраховуйте голоси +Досліджуйте: Популярні Хештеги й Користувачі за одним дотиком +• Сповіщення: отримуйте сповіщення про нових підписників, відповіді та репости +Діліться: Публікуйте безпосередньо в Mastodon з будь-якого меню "поділитися" в будь-якому додатку +• Привабливість: Нашим талісманом є чарівний слон, і ви побачите, як він з'являється час від часу -Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way. \ No newline at end of file +Mastodon є зареєстрованою некомерційною організацією і розробка підтримується безпосередньо вашими пожертвуваннями. Тут немає реклами, монетизації та венчурного капіталу, і плануємо так тримати. \ No newline at end of file diff --git a/fastlane/metadata/android/uk-UA/short_description.txt b/fastlane/metadata/android/uk-UA/short_description.txt index 8f5a9b847..4e4dcd741 100644 --- a/fastlane/metadata/android/uk-UA/short_description.txt +++ b/fastlane/metadata/android/uk-UA/short_description.txt @@ -1 +1 @@ -Decentralized social network \ No newline at end of file +Децентралізована соціальна мережа \ No newline at end of file diff --git a/mastodon/build.gradle b/mastodon/build.gradle index e4d34da02..c32cc5498 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -19,7 +19,7 @@ android { applicationId "org.joinmastodon.android.git" minSdk 23 targetSdk 33 - versionCode 43 + versionCode 45 versionName "1.1.4+${getGitHash()}" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java index 9ad3532cc..8b222c6dc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java @@ -1,20 +1,32 @@ package org.joinmastodon.android.fragments; -import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.text.SpannableString; +import android.text.style.ImageSpan; +import android.text.style.ReplacementSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowInsets; +import android.widget.LinearLayout; +import android.widget.TextView; import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; -import org.joinmastodon.android.fragments.onboarding.InstanceCatalogFragment; +import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment; +import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment; import org.joinmastodon.android.ui.InterpolatingMotionEffect; import org.joinmastodon.android.ui.views.SizeListenerFrameLayout; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager2.widget.ViewPager2; import me.grishka.appkit.Nav; import me.grishka.appkit.fragments.AppKitFragment; import me.grishka.appkit.utils.V; @@ -23,12 +35,13 @@ public class SplashFragment extends AppKitFragment{ private SizeListenerFrameLayout contentView; private View artContainer, blueFill, greenFill; - private InterpolatingMotionEffect motionEffect; + private ViewPager2 pager; + private ViewGroup pagerDots; + private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); - motionEffect=new InterpolatingMotionEffect(MastodonApp.context); } @Nullable @@ -37,15 +50,44 @@ public class SplashFragment extends AppKitFragment{ contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false); contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick); contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick); + artClouds=contentView.findViewById(R.id.art_clouds); + artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant); + artRightHill=contentView.findViewById(R.id.art_right_hill); + artLeftHill=contentView.findViewById(R.id.art_left_hill); + artCenterHill=contentView.findViewById(R.id.art_center_hill); + pager=contentView.findViewById(R.id.pager); + pagerDots=contentView.findViewById(R.id.pager_dots); + pager.setAdapter(new PagerAdapter()); + pager.setOffscreenPageLimit(3); + pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){ + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){ + for(int i=0;i=1 ? 1f : positionOffset)); + artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress); + artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress); + artLeftHill.setTranslationY(V.dp(24)*parallaxProgress); + artRightHill.setTranslationX(V.dp(-88)*parallaxProgress); + artRightHill.setTranslationY(V.dp(-24)*parallaxProgress); + artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress); + } + }); artContainer=contentView.findViewById(R.id.art_container); blueFill=contentView.findViewById(R.id.blue_fill); greenFill=contentView.findViewById(R.id.green_fill); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_clouds), V.dp(-5), V.dp(5), V.dp(-5), V.dp(5))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_right_hill), V.dp(-15), V.dp(25), V.dp(-10), V.dp(10))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_left_hill), V.dp(-25), V.dp(15), V.dp(-15), V.dp(15))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_center_hill), V.dp(-14), V.dp(14), V.dp(-5), V.dp(25))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_plane_elephant), V.dp(-20), V.dp(12), V.dp(-20), V.dp(12))); contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){ @Override @@ -66,15 +108,16 @@ public class SplashFragment extends AppKitFragment{ private void onButtonClick(View v){ Bundle extras=new Bundle(); - extras.putBoolean("signup", v.getId()==R.id.btn_get_started); - Nav.go(getActivity(), InstanceCatalogFragment.class, extras); + boolean isSignup=v.getId()==R.id.btn_get_started; + extras.putBoolean("signup", isSignup); + Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras); } private void updateArtSize(int w, int h){ - float scale=w/(float)V.dp(412); + float scale=w/(float)V.dp(360); artContainer.setScaleX(scale); artContainer.setScaleY(scale); - blueFill.setScaleY(h/2f); + blueFill.setScaleY(artContainer.getBottom()-V.dp(90)); greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90)); } @@ -100,15 +143,91 @@ public class SplashFragment extends AppKitFragment{ return true; } - @Override - protected void onShown(){ - super.onShown(); - motionEffect.activate(); + private class PagerAdapter extends RecyclerView.Adapter{ + + @NonNull + @Override + public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new PagerViewHolder(viewType); + } + + @Override + public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){} + + @Override + public int getItemCount(){ + return 3; + } + + @Override + public int getItemViewType(int position){ + return position; + } } - @Override - protected void onHidden(){ - super.onHidden(); - motionEffect.deactivate(); + private class PagerViewHolder extends RecyclerView.ViewHolder{ + public PagerViewHolder(int page){ + super(new LinearLayout(getActivity())); + LinearLayout ll=(LinearLayout) itemView; + ll.setOrientation(LinearLayout.VERTICAL); + int pad=V.dp(16); + ll.setPadding(pad, pad, pad, pad); + ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + TextView title=new TextView(getActivity()); + title.setTextAppearance(R.style.m3_headline_medium); + title.setText(switch(page){ + case 0 -> { + String src=getString(R.string.welcome_page1_title); + SpannableString ss=new SpannableString(src); + int start=src.indexOf("{logo}"); + if(start!=-1){ + LogoSpan span=new LogoSpan(getResources().getDrawable(R.drawable.splash_logo, getActivity().getTheme())); + ss.setSpan(span, start, start+6, 0); + } + yield ss; + } + case 1 -> getString(R.string.welcome_page2_title); + case 2 -> getString(R.string.welcome_page3_title); + default -> throw new IllegalStateException("Unexpected value: "+page); + }); + title.setTextColor(0xFF17063B); + LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36)); + lp.bottomMargin=V.dp(page==0 ? 4 : 14); + ll.addView(title, lp); + + TextView text=new TextView(getActivity()); + text.setTextAppearance(R.style.m3_body_medium); + text.setText(switch(page){ + case 0 -> R.string.welcome_page1_text; + case 1 -> R.string.welcome_page2_text; + case 2 -> R.string.welcome_page3_text; + default -> throw new IllegalStateException("Unexpected value: "+page); + }); + text.setTextColor(0xFF17063B); + ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } + } + + private class LogoSpan extends ReplacementSpan{ + private final Drawable drawable; + + private LogoSpan(Drawable drawable){ + this.drawable=drawable; + } + + @Override + public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){ + return drawable.getIntrinsicWidth(); + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){ + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + canvas.save(); + canvas.translate(x, y-V.dp(20)); + drawable.draw(canvas); + canvas.restore(); + } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java index d23b8339e..a188711b4 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java @@ -2,46 +2,30 @@ package org.joinmastodon.android.fragments.onboarding; import android.app.Activity; import android.app.ProgressDialog; -import android.content.Context; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.LocaleList; -import android.text.Editable; import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; -import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; -import android.widget.ImageView; import android.widget.RadioButton; import android.widget.TextView; import org.joinmastodon.android.R; import org.joinmastodon.android.api.MastodonAPIController; -import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonErrorResponse; import org.joinmastodon.android.api.requests.instance.GetInstance; -import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories; -import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances; -import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.model.Instance; -import org.joinmastodon.android.model.catalog.CatalogCategory; import org.joinmastodon.android.model.catalog.CatalogInstance; -import org.joinmastodon.android.ui.BetterItemAnimator; -import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.M3AlertDialogBuilder; -import org.joinmastodon.android.ui.tabs.TabLayout; import org.joinmastodon.android.ui.utils.UiUtils; -import org.parceler.Parcels; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; @@ -59,49 +43,42 @@ import java.util.stream.Collectors; import javax.xml.parsers.DocumentBuilderFactory; import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; -import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.MergeRecyclerAdapter; -import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.V; import me.grishka.appkit.views.UsableRecyclerView; import okhttp3.Call; import okhttp3.Request; import okhttp3.Response; -public class InstanceCatalogFragment extends BaseRecyclerFragment{ - private InstancesAdapter adapter; - private MergeRecyclerAdapter mergeAdapter; - private View headerView; - private CatalogInstance chosenInstance; - private List filteredData=new ArrayList<>(); - private Button nextButton; - private MastodonAPIRequest getCategoriesRequest; - private EditText searchEdit; - private TabLayout categoriesList; - private Runnable searchDebouncer=this::onSearchChangedDebounced; - private String currentSearchQuery; - private String currentCategory="all"; - private List categories=new ArrayList<>(); - private String loadingInstanceDomain; - private GetInstance loadingInstanceRequest; - private Call loadingInstanceRedirectRequest; - private HashMap instancesCache=new HashMap<>(); - private ProgressDialog instanceProgressDialog; - private View buttonBar; - private HashMap redirects=new HashMap<>(), redirectsInverse=new HashMap<>(); - - private boolean isSignup; +abstract class InstanceCatalogFragment extends BaseRecyclerFragment{ + protected RecyclerView.Adapter adapter; + protected MergeRecyclerAdapter mergeAdapter; + protected CatalogInstance chosenInstance; + protected Button nextButton; + protected EditText searchEdit; + protected Runnable searchDebouncer=this::onSearchChangedDebounced; + protected String currentSearchQuery; + protected String loadingInstanceDomain; + protected HashMap instancesCache=new HashMap<>(); + protected View buttonBar; + protected List filteredData=new ArrayList<>(); + protected GetInstance loadingInstanceRequest; + protected Call loadingInstanceRedirectRequest; + protected ProgressDialog instanceProgressDialog; + protected HashMap redirects=new HashMap<>(); + protected HashMap redirectsInverse=new HashMap<>(); + protected boolean isSignup; + protected CatalogInstance fakeInstance=new CatalogInstance(); private static final double DUNBAR=Math.log(800); - public InstanceCatalogFragment(){ - super(R.layout.fragment_onboarding_common, 10); + public InstanceCatalogFragment(int layout, int perPage){ + super(layout, perPage); } @Override @@ -110,266 +87,9 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment(){ - @Override - public void onSuccess(List result){ - if(getActivity()==null) - return; - Map> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language)); - for(List group:byLang.values()){ - Collections.sort(group, (a, b)->{ - double aa=Math.abs(DUNBAR-Math.log(a.lastWeekUsers)); - double bb=Math.abs(DUNBAR-Math.log(b.lastWeekUsers)); - return Double.compare(aa, bb); - }); - } - // get the list of user-configured system languages - List userLangs; - if(Build.VERSION.SDK_INT<24){ - userLangs=Collections.singletonList(getResources().getConfiguration().locale.getLanguage()); - }else{ - LocaleList ll=getResources().getConfiguration().getLocales(); - userLangs=new ArrayList<>(ll.size()); - for(int i=0;i sortedList=new ArrayList<>(); - for(String lang:userLangs){ - List langInstances=byLang.remove(lang); - if(langInstances!=null){ - sortedList.addAll(langInstances); - } - } - // sort the remaining language groups by aggregate lastWeekUsers - class InstanceGroup{ - public int activeUsers; - public List instances; - } - byLang.values().stream().map(il->{ - InstanceGroup group=new InstanceGroup(); - group.instances=il; - for(CatalogInstance instance:il){ - group.activeUsers+=instance.lastWeekUsers; - } - return group; - }).sorted(Comparator.comparingInt((InstanceGroup g)->g.activeUsers).reversed()).forEachOrdered(ig->sortedList.addAll(ig.instances)); - onDataLoaded(sortedList, false); - updateFilteredList(); - } - - @Override - public void onError(ErrorResponse error){ - error.showToast(getActivity()); - onDataLoaded(Collections.emptyList(), false); - } - }) - .execNoAuth(""); - getCategoriesRequest=new GetCatalogCategories(null) - .setCallback(new Callback<>(){ - @Override - public void onSuccess(List result){ - getCategoriesRequest=null; - CatalogCategory all=new CatalogCategory(); - all.category="all"; - categories.add(all); - result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add); - updateCategories(); - } - - @Override - public void onError(ErrorResponse error){ - getCategoriesRequest=null; - error.showToast(getActivity()); - CatalogCategory all=new CatalogCategory(); - all.category="all"; - categories.add(all); - updateCategories(); - } - }) - .execNoAuth(""); - } - - private void updateCategories(){ - categoriesList.removeAllTabs(); - for(CatalogCategory cat:categories){ - int titleRes=getTitleForCategory(cat.category); - TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category); - ImageView emoji=tab.getCustomView().findViewById(R.id.emoji); - emoji.setImageResource(getEmojiForCategory(cat.category)); - categoriesList.addTab(tab); - } - } - - @Override - public void onDestroy(){ - super.onDestroy(); - if(getCategoriesRequest!=null) - getCategoriesRequest.cancel(); - } - - @Override - protected RecyclerView.Adapter getAdapter(){ - headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false); - searchEdit=headerView.findViewById(R.id.search_edit); - categoriesList=headerView.findViewById(R.id.categories_list); - categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){ - @Override - public void onTabSelected(TabLayout.Tab tab){ - CatalogCategory category=categories.get(tab.getPosition()); - currentCategory=category.category; - updateFilteredList(); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab){ - - } - - @Override - public void onTabReselected(TabLayout.Tab tab){ - - } - }); - searchEdit.setOnEditorActionListener(this::onSearchEnterPressed); - searchEdit.addTextChangedListener(new TextWatcher(){ - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after){ - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count){ - searchEdit.removeCallbacks(searchDebouncer); - searchEdit.postDelayed(searchDebouncer, 300); - } - - @Override - public void afterTextChanged(Editable s){ - } - }); - - mergeAdapter=new MergeRecyclerAdapter(); - mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); - mergeAdapter.addAdapter(adapter=new InstancesAdapter()); - return mergeAdapter; - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState){ - super.onViewCreated(view, savedInstanceState); - nextButton=view.findViewById(R.id.btn_next); - nextButton.setOnClickListener(this::onNextClick); - nextButton.setEnabled(chosenInstance!=null); - view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); - list.setItemAnimator(new BetterItemAnimator()); - list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST)); - view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); - buttonBar=view.findViewById(R.id.button_bar); - setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); - } - - private void onNextClick(View v){ - String domain=chosenInstance.domain; - Instance instance=instancesCache.get(domain); - if(instance!=null){ - proceedWithAuthOrSignup(instance); - }else{ - showProgressDialog(); - if(!domain.equals(loadingInstanceDomain)){ - loadInstanceInfo(domain, false); - } - } - } - - private void proceedWithAuthOrSignup(Instance instance){ - getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0); - if(isSignup){ - if(!instance.registrations){ - new M3AlertDialogBuilder(getActivity()) - .setTitle(R.string.error) - .setMessage(R.string.instance_signup_closed) - .setPositiveButton(R.string.ok, null) - .show(); - return; - } - Bundle args=new Bundle(); - args.putParcelable("instance", Parcels.wrap(instance)); - Nav.go(getActivity(), InstanceRulesFragment.class, args); - }else{ - AccountSessionManager.getInstance().authenticate(getActivity(), instance); - } - } - -// private String getEmojiForCategory(String category){ -// return switch(category){ -// case "all" -> "💬"; -// case "academia" -> "📚"; -// case "activism" -> "✊"; -// case "food" -> "🍕"; -// case "furry" -> "🦁"; -// case "games" -> "🕹"; -// case "general" -> "🐘"; -// case "journalism" -> "📰"; -// case "lgbt" -> "🏳️‍🌈"; -// case "regional" -> "📍"; -// case "art" -> "🎨"; -// case "music" -> "🎼"; -// case "tech" -> "📱"; -// default -> "❓"; -// }; -// } - - private int getEmojiForCategory(String category){ - return switch(category){ - case "all" -> R.drawable.ic_category_all; - case "academia" -> R.drawable.ic_category_academia; - case "activism" -> R.drawable.ic_category_activism; - case "food" -> R.drawable.ic_category_food; - case "furry" -> R.drawable.ic_category_furry; - case "games" -> R.drawable.ic_category_games; - case "general" -> R.drawable.ic_category_general; - case "journalism" -> R.drawable.ic_category_journalism; - case "lgbt" -> R.drawable.ic_category_lgbt; - case "regional" -> R.drawable.ic_category_regional; - case "art" -> R.drawable.ic_category_art; - case "music" -> R.drawable.ic_category_music; - case "tech" -> R.drawable.ic_category_tech; - default -> R.drawable.ic_category_unknown; - }; - } - - private int getTitleForCategory(String category){ - return switch(category){ - case "all" -> R.string.category_all; - case "academia" -> R.string.category_academia; - case "activism" -> R.string.category_activism; - case "food" -> R.string.category_food; - case "furry" -> R.string.category_furry; - case "games" -> R.string.category_games; - case "general" -> R.string.category_general; - case "journalism" -> R.string.category_journalism; - case "lgbt" -> R.string.category_lgbt; - case "regional" -> R.string.category_regional; - case "art" -> R.string.category_art; - case "music" -> R.string.category_music; - case "tech" -> R.string.category_tech; - default -> 0; - }; - } - - private boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){ + protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){ if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN) return true; currentSearchQuery=searchEdit.getText().toString().toLowerCase(); @@ -385,60 +105,73 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment prevData=new ArrayList<>(filteredData); - filteredData.clear(); - for(CatalogInstance instance:data){ - if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){ - if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){ - if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired) - filteredData.add(instance); - } + protected List sortInstances(List result){ + Map> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language)); + for(List group:byLang.values()){ + Collections.sort(group, (a, b)->{ + double aa=Math.abs(DUNBAR-Math.log(a.lastWeekUsers)); + double bb=Math.abs(DUNBAR-Math.log(b.lastWeekUsers)); + return Double.compare(aa, bb); + }); + } + // get the list of user-configured system languages + List userLangs; + if(Build.VERSION.SDK_INT<24){ + userLangs=Collections.singletonList(getResources().getConfiguration().locale.getLanguage()); + }else{ + LocaleList ll=getResources().getConfiguration().getLocales(); + userLangs=new ArrayList<>(ll.size()); + for(int i=0;i sortedList=new ArrayList<>(); + for(String lang:userLangs){ + List langInstances=byLang.remove(lang); + if(langInstances!=null){ + sortedList.addAll(langInstances); } - - @Override - public int getNewListSize(){ - return filteredData.size(); + } + // sort the remaining language groups by aggregate lastWeekUsers + class InstanceGroup{ + public int activeUsers; + public List instances; + } + byLang.values().stream().map(il->{ + InstanceGroup group=new InstanceGroup(); + group.instances=il; + for(CatalogInstance instance:il){ + group.activeUsers+=instance.lastWeekUsers; } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){ - return prevData.get(oldItemPosition)==filteredData.get(newItemPosition); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){ - return prevData.get(oldItemPosition)==filteredData.get(newItemPosition); - } - }).dispatchUpdatesTo(adapter); + return group; + }).sorted(Comparator.comparingInt((InstanceGroup g)->g.activeUsers).reversed()).forEachOrdered(ig->sortedList.addAll(ig.instances)); + return sortedList; } - private void showProgressDialog(){ + protected abstract void updateFilteredList(); + + protected void showProgressDialog(){ instanceProgressDialog=new ProgressDialog(getActivity()); instanceProgressDialog.setMessage(getString(R.string.loading_instance)); instanceProgressDialog.setOnCancelListener(dialog->cancelLoadingInstanceInfo()); instanceProgressDialog.show(); } - private String normalizeInstanceDomain(String _domain){ + protected String normalizeInstanceDomain(String _domain){ if(TextUtils.isEmpty(_domain)) return null; if(_domain.contains(":")){ try{ _domain=Uri.parse(_domain).getAuthority(); - }catch(Exception ignore){} + }catch(Exception ignore){ + } if(TextUtils.isEmpty(_domain)) return null; } @@ -453,12 +186,12 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment(){ - @Override - public void onSuccess(Instance result){ - loadingInstanceRequest=null; - loadingInstanceDomain=null; - result.uri=domain; // needed for instances that use domain redirection - instancesCache.put(domain, result); - if(instanceProgressDialog!=null){ - instanceProgressDialog.dismiss(); - instanceProgressDialog=null; - proceedWithAuthOrSignup(result); - } - if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){ - boolean found=false; - for(CatalogInstance ci:filteredData){ - if(ci.domain.equals(domain)){ - found=true; - break; - } - } - if(!found){ - CatalogInstance ci=result.toCatalogInstance(); - filteredData.add(0, ci); - adapter.notifyItemInserted(0); - } + @Override + public void onSuccess(Instance result){ + loadingInstanceRequest=null; + loadingInstanceDomain=null; + result.uri=domain; // needed for instances that use domain redirection + instancesCache.put(domain, result); + if(instanceProgressDialog!=null){ + instanceProgressDialog.dismiss(); + instanceProgressDialog=null; + proceedWithAuthOrSignup(result); + } + if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){ + boolean found=false; + for(CatalogInstance ci : filteredData){ + if(ci.domain.equals(domain) && ci!=fakeInstance){ + found=true; + break; } } + if(!found){ + CatalogInstance ci=result.toCatalogInstance(); + if(filteredData.size()==1 && filteredData.get(0)==fakeInstance){ + filteredData.set(0, ci); + adapter.notifyItemChanged(0); + }else{ + filteredData.add(0, ci); + adapter.notifyItemInserted(0); + } + } + } + } - @Override - public void onError(ErrorResponse error){ - loadingInstanceRequest=null; - if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){ - fetchDomainFromHostMetaAndMaybeRetry(domain, error); - return; + @Override + public void onError(ErrorResponse error){ + loadingInstanceRequest=null; + if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){ + fetchDomainFromHostMetaAndMaybeRetry(domain, error); + return; + } + loadingInstanceDomain=null; + showInstanceInfoLoadError(domain, error); + if(fakeInstance!=null){ + fakeInstance.description=getString(R.string.error); + if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){ + if(list.findViewHolderForAdapterPosition(1) instanceof BindableViewHolder ivh){ + ivh.rebind(); } - loadingInstanceDomain=null; - showInstanceInfoLoadError(domain, error); } - }).execNoAuth(domain); + } + } + }).execNoAuth(domain); } private void cancelLoadingInstanceInfo(){ @@ -584,7 +330,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment{ - public InstancesAdapter(){ - super(imgLoader); - } - - @NonNull - @Override - public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ - return new InstanceViewHolder(); - } - - @Override - public void onBindViewHolder(InstanceViewHolder holder, int position){ - holder.bind(filteredData.get(position)); - super.onBindViewHolder(holder, position); - } - - @Override - public int getItemCount(){ - return filteredData.size(); - } - - @Override - public int getItemViewType(int position){ - return -1; - } + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + nextButton=view.findViewById(R.id.btn_next); + nextButton.setOnClickListener(this::onNextClick); + nextButton.setEnabled(chosenInstance!=null); + buttonBar=view.findViewById(R.id.button_bar); + setRefreshEnabled(false); } - private class InstanceViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ - private final TextView title, description, userCount, lang; - private final RadioButton radioButton; - - public InstanceViewHolder(){ - super(getActivity(), R.layout.item_instance_catalog, list); - title=findViewById(R.id.title); - description=findViewById(R.id.description); - userCount=findViewById(R.id.user_count); - lang=findViewById(R.id.lang); - radioButton=findViewById(R.id.radiobtn); - if(Build.VERSION.SDK_INT getCategoriesRequest; + private TabLayout categoriesList; + private String currentCategory="all"; + private List categories=new ArrayList<>(); + + + public InstanceCatalogSignupFragment(){ + super(R.layout.fragment_onboarding_common, 10); + } + + @Override + public void onAttach(Context context){ + super.onAttach(context); + setRefreshEnabled(false); + loadData(); + } + + @Override + protected void doLoadData(int offset, int count){ + currentRequest=new GetCatalogInstances(null, null) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(List result){ + if(getActivity()==null) + return; + onDataLoaded(sortInstances(result), false); + updateFilteredList(); + } + + @Override + public void onError(ErrorResponse error){ + error.showToast(getActivity()); + onDataLoaded(Collections.emptyList(), false); + } + }) + .execNoAuth(""); + getCategoriesRequest=new GetCatalogCategories(null) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(List result){ + getCategoriesRequest=null; + CatalogCategory all=new CatalogCategory(); + all.category="all"; + categories.add(all); + result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add); + updateCategories(); + } + + @Override + public void onError(ErrorResponse error){ + getCategoriesRequest=null; + error.showToast(getActivity()); + CatalogCategory all=new CatalogCategory(); + all.category="all"; + categories.add(all); + updateCategories(); + } + }) + .execNoAuth(""); + } + + private void updateCategories(){ + categoriesList.removeAllTabs(); + for(CatalogCategory cat:categories){ + int titleRes=getTitleForCategory(cat.category); + TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category); + ImageView emoji=tab.getCustomView().findViewById(R.id.emoji); + emoji.setImageResource(getEmojiForCategory(cat.category)); + categoriesList.addTab(tab); + } + } + + @Override + public void onDestroy(){ + super.onDestroy(); + if(getCategoriesRequest!=null) + getCategoriesRequest.cancel(); + } + + @Override + protected RecyclerView.Adapter getAdapter(){ + headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false); + searchEdit=headerView.findViewById(R.id.search_edit); + categoriesList=headerView.findViewById(R.id.categories_list); + categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){ + @Override + public void onTabSelected(TabLayout.Tab tab){ + CatalogCategory category=categories.get(tab.getPosition()); + currentCategory=category.category; + updateFilteredList(); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab){ + + } + + @Override + public void onTabReselected(TabLayout.Tab tab){ + + } + }); + searchEdit.setOnEditorActionListener(this::onSearchEnterPressed); + searchEdit.addTextChangedListener(new TextWatcher(){ + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after){ + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count){ + searchEdit.removeCallbacks(searchDebouncer); + searchEdit.postDelayed(searchDebouncer, 300); + } + + @Override + public void afterTextChanged(Editable s){ + } + }); + + mergeAdapter=new MergeRecyclerAdapter(); + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); + mergeAdapter.addAdapter(adapter=new InstancesAdapter()); + return mergeAdapter; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); + list.setItemAnimator(new BetterItemAnimator()); + list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST)); + view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + } + + @Override + protected void proceedWithAuthOrSignup(Instance instance){ + getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0); + if(isSignup){ + if(!instance.registrations){ + new M3AlertDialogBuilder(getActivity()) + .setTitle(R.string.error) + .setMessage(R.string.instance_signup_closed) + .setPositiveButton(R.string.ok, null) + .show(); + return; + } + Bundle args=new Bundle(); + args.putParcelable("instance", Parcels.wrap(instance)); + Nav.go(getActivity(), InstanceRulesFragment.class, args); + }else{ + } + } + +// private String getEmojiForCategory(String category){ +// return switch(category){ +// case "all" -> "💬"; +// case "academia" -> "📚"; +// case "activism" -> "✊"; +// case "food" -> "🍕"; +// case "furry" -> "🦁"; +// case "games" -> "🕹"; +// case "general" -> "🐘"; +// case "journalism" -> "📰"; +// case "lgbt" -> "🏳️‍🌈"; +// case "regional" -> "📍"; +// case "art" -> "🎨"; +// case "music" -> "🎼"; +// case "tech" -> "📱"; +// default -> "❓"; +// }; +// } + + private int getEmojiForCategory(String category){ + return switch(category){ + case "all" -> R.drawable.ic_category_all; + case "academia" -> R.drawable.ic_category_academia; + case "activism" -> R.drawable.ic_category_activism; + case "food" -> R.drawable.ic_category_food; + case "furry" -> R.drawable.ic_category_furry; + case "games" -> R.drawable.ic_category_games; + case "general" -> R.drawable.ic_category_general; + case "journalism" -> R.drawable.ic_category_journalism; + case "lgbt" -> R.drawable.ic_category_lgbt; + case "regional" -> R.drawable.ic_category_regional; + case "art" -> R.drawable.ic_category_art; + case "music" -> R.drawable.ic_category_music; + case "tech" -> R.drawable.ic_category_tech; + default -> R.drawable.ic_category_unknown; + }; + } + + private int getTitleForCategory(String category){ + return switch(category){ + case "all" -> R.string.category_all; + case "academia" -> R.string.category_academia; + case "activism" -> R.string.category_activism; + case "food" -> R.string.category_food; + case "furry" -> R.string.category_furry; + case "games" -> R.string.category_games; + case "general" -> R.string.category_general; + case "journalism" -> R.string.category_journalism; + case "lgbt" -> R.string.category_lgbt; + case "regional" -> R.string.category_regional; + case "art" -> R.string.category_art; + case "music" -> R.string.category_music; + case "tech" -> R.string.category_tech; + default -> 0; + }; + } + + @Override + protected void updateFilteredList(){ + ArrayList prevData=new ArrayList<>(filteredData); + filteredData.clear(); + for(CatalogInstance instance:data){ + if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){ + if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){ + if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired) + filteredData.add(instance); + } + } + } + DiffUtil.calculateDiff(new DiffUtil.Callback(){ + @Override + public int getOldListSize(){ + return prevData.size(); + } + + @Override + public int getNewListSize(){ + return filteredData.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){ + return prevData.get(oldItemPosition)==filteredData.get(newItemPosition); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){ + return prevData.get(oldItemPosition)==filteredData.get(newItemPosition); + } + }).dispatchUpdatesTo(adapter); + } + + + private class InstancesAdapter extends UsableRecyclerView.Adapter{ + public InstancesAdapter(){ + super(imgLoader); + } + + @NonNull + @Override + public InstanceCatalogSignupFragment.InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new InstanceCatalogSignupFragment.InstanceViewHolder(); + } + + @Override + public void onBindViewHolder(InstanceCatalogSignupFragment.InstanceViewHolder holder, int position){ + holder.bind(filteredData.get(position)); + super.onBindViewHolder(holder, position); + } + + @Override + public int getItemCount(){ + return filteredData.size(); + } + + @Override + public int getItemViewType(int position){ + return -1; + } + } + + private class InstanceViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ + private final TextView title, description, userCount, lang; + private final RadioButton radioButton; + + public InstanceViewHolder(){ + super(getActivity(), R.layout.item_instance_catalog, list); + title=findViewById(R.id.title); + description=findViewById(R.id.description); + userCount=findViewById(R.id.user_count); + lang=findViewById(R.id.lang); + radioButton=findViewById(R.id.radiobtn); + if(Build.VERSION.SDK_INT prevData=new ArrayList<>(filteredData); + filteredData.clear(); + if(currentSearchQuery.length()>0){ + boolean foundExactMatch=false; + for(CatalogInstance inst:data){ + if(inst.normalizedDomain.contains(currentSearchQuery)){ + filteredData.add(inst); + if(inst.normalizedDomain.equals(currentSearchQuery)) + foundExactMatch=true; + } + } + if(!foundExactMatch) + filteredData.add(0, fakeInstance); + } + UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals); + for(int i=0;i(){ + @Override + public void onSuccess(List result){ + data.clear(); + data.addAll(sortInstances(result)); + } + + @Override + public void onError(ErrorResponse error){ + + } + }) + .execNoAuth(""); + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + Toolbar toolbar=getToolbar(); + toolbar.setElevation(0); + toolbar.setBackground(null); + } + + @Override + protected RecyclerView.Adapter getAdapter(){ + headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_login, list, false); + clearBtn=headerView.findViewById(R.id.search_clear); + searchEdit=headerView.findViewById(R.id.search_edit); + searchEdit.setOnEditorActionListener(this::onSearchEnterPressed); + searchEdit.addTextChangedListener(new TextWatcher(){ + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after){ + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count){ + searchEdit.removeCallbacks(searchDebouncer); + searchEdit.postDelayed(searchDebouncer, 300); + + if(s.length()>0){ + fakeInstance.domain=fakeInstance.normalizedDomain=s.toString(); + fakeInstance.description=getString(R.string.loading_instance); + if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){ + if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){ + ivh.rebind(); + } + } + if(filteredData.isEmpty()){ + filteredData.add(fakeInstance); + adapter.notifyItemInserted(0); + } + clearBtn.setVisibility(View.VISIBLE); + }else{ + clearBtn.setVisibility(View.GONE); + } + } + + @Override + public void afterTextChanged(Editable s){ + } + }); + clearBtn.setOnClickListener(v->searchEdit.setText("")); + + mergeAdapter=new MergeRecyclerAdapter(); + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); + mergeAdapter.addAdapter(adapter=new InstancesAdapter()); + return mergeAdapter; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + + list.addItemDecoration(new RecyclerView.ItemDecoration(){ + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){ + if(parent.getChildViewHolder(view) instanceof InstanceViewHolder){ + outRect.left=outRect.right=V.dp(16); + } + } + }); + ((UsableRecyclerView)list).setDrawSelectorOnTop(true); + } + + private class InstancesAdapter extends UsableRecyclerView.Adapter{ + public InstancesAdapter(){ + super(imgLoader); + } + + @NonNull + @Override + public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new InstanceViewHolder(); + } + + @Override + public void onBindViewHolder(InstanceViewHolder holder, int position){ + holder.bind(filteredData.get(position)); + super.onBindViewHolder(holder, position); + } + + @Override + public int getItemCount(){ + return filteredData.size(); + } + + @Override + public int getItemViewType(int position){ + return -1; + } + } + + private class InstanceViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ + private final TextView title, description; + private final RadioButton radioButton; + + public InstanceViewHolder(){ + super(getActivity(), R.layout.item_instance_login, list); + title=findViewById(R.id.title); + description=findViewById(R.id.description); + radioButton=findViewById(R.id.radiobtn); + radioButton.setMinWidth(0); + radioButton.setMinHeight(0); + + itemView.setOutlineProvider(new ViewOutlineProvider(){ + @Override + public void getOutline(View view, Outline outline){ + outline.setRoundRect(0, getAbsoluteAdapterPosition()==1 ? 0 : V.dp(-4), view.getWidth(), view.getHeight()+(getAbsoluteAdapterPosition()==filteredData.size() ? 0 : V.dp(4)), V.dp(4)); + } + }); + itemView.setClipToOutline(true); + } + + @Override + public void onBind(CatalogInstance item){ + title.setText(item.normalizedDomain); + description.setText(item.description); + radioButton.setChecked(chosenInstance==item); + } + + @Override + public void onClick(){ + if(chosenInstance==item) + return; + if(chosenInstance!=null){ + int idx=filteredData.indexOf(chosenInstance); + if(idx!=-1){ + boolean found=false; + for(int i=0;i oldList=emojis; - emojis=AccountSessionManager.getInstance() + List allEmojis = AccountSessionManager.getInstance() .getCustomEmojis(AccountSessionManager.getInstance().getAccount(accountID).domain) .stream() .flatMap(ec->ec.emojis.stream()) - .filter(e->e.visibleInPicker && e.shortcode.startsWith(_text)) + .filter(e->e.visibleInPicker) + .collect(Collectors.toList()); + List startsWithSearch = allEmojis.stream().filter(e -> e.shortcode.toLowerCase().startsWith(_text.toLowerCase())).collect(Collectors.toList()); + emojis=Stream.concat(startsWithSearch.stream(), allEmojis.stream() + .filter(e -> !startsWithSearch.contains(e)) + .filter(e -> e.shortcode.toLowerCase().contains(_text.toLowerCase()))) .map(WrappedEmoji::new) .collect(Collectors.toList()); UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode)); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FloatingHintEditTextLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FloatingHintEditTextLayout.java new file mode 100644 index 000000000..6e59ead59 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FloatingHintEditTextLayout.java @@ -0,0 +1,124 @@ +package org.joinmastodon.android.ui.views; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.text.Editable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.ui.utils.SimpleTextWatcher; + +import me.grishka.appkit.utils.CubicBezierInterpolator; +import me.grishka.appkit.utils.V; + +public class FloatingHintEditTextLayout extends FrameLayout{ + private EditText edit; + private TextView label; + private int labelTextSize; + private int offsetY; + private boolean hintVisible; + private Animator currentAnim; + + public FloatingHintEditTextLayout(Context context){ + this(context, null); + } + + public FloatingHintEditTextLayout(Context context, AttributeSet attrs){ + this(context, attrs, 0); + } + + public FloatingHintEditTextLayout(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + if(isInEditMode()) + V.setApplicationContext(context); + TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FloatingHintEditTextLayout); + labelTextSize=ta.getDimensionPixelSize(R.styleable.FloatingHintEditTextLayout_android_labelTextSize, V.dp(12)); + offsetY=ta.getDimensionPixelOffset(R.styleable.FloatingHintEditTextLayout_editTextOffsetY, 0); + ta.recycle(); + } + + @Override + protected void onFinishInflate(){ + super.onFinishInflate(); + if(getChildCount()>0 && getChildAt(0) instanceof EditText et){ + edit=et; + }else{ + throw new IllegalStateException("First child must be an EditText"); + } + + label=new TextView(getContext()); + label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize); + label.setTextColor(edit.getHintTextColors()); + label.setText(edit.getHint()); + label.setSingleLine(); + label.setPivotX(0f); + label.setPivotY(0f); + LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP); + lp.setMarginStart(edit.getPaddingStart()); + addView(label, lp); + + hintVisible=edit.getText().length()==0; + if(hintVisible) + label.setAlpha(0f); + + edit.addTextChangedListener(new SimpleTextWatcher(this::onTextChanged)); + } + + private void onTextChanged(Editable text){ + boolean newHintVisible=text.length()==0; + if(newHintVisible==hintVisible) + return; + if(currentAnim!=null) + currentAnim.cancel(); + hintVisible=newHintVisible; + + label.setAlpha(1); + float scale=edit.getLineHeight()/(float)label.getLineHeight(); + float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f); + + AnimatorSet anim=new AnimatorSet(); + if(hintVisible){ + anim.playTogether( + ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0), + ObjectAnimator.ofFloat(label, SCALE_X, scale), + ObjectAnimator.ofFloat(label, SCALE_Y, scale), + ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY) + ); + edit.setHintTextColor(0); + }else{ + label.setScaleX(scale); + label.setScaleY(scale); + label.setTranslationY(transY); + anim.playTogether( + ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY), + ObjectAnimator.ofFloat(label, SCALE_X, 1f), + ObjectAnimator.ofFloat(label, SCALE_Y, 1f), + ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f) + ); + } + anim.setDuration(150); + anim.setInterpolator(CubicBezierInterpolator.DEFAULT); + anim.start(); + anim.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + currentAnim=null; + if(hintVisible){ + label.setAlpha(0); + edit.setHintTextColor(label.getTextColors()); + } + } + }); + currentAnim=anim; + } +} diff --git a/mastodon/src/main/res/color/button_text_m3_filled.xml b/mastodon/src/main/res/color/button_text_m3_filled.xml new file mode 100644 index 000000000..84416b4f9 --- /dev/null +++ b/mastodon/src/main/res/color/button_text_m3_filled.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/button_text_m3_text.xml b/mastodon/src/main/res/color/button_text_m3_text.xml new file mode 100644 index 000000000..a2bb33cf2 --- /dev/null +++ b/mastodon/src/main/res/color/button_text_m3_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_pressed_overlay.xml b/mastodon/src/main/res/color/m3_pressed_overlay.xml new file mode 100644 index 000000000..824b4b289 --- /dev/null +++ b/mastodon/src/main/res/color/m3_pressed_overlay.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_primary_overlay.xml b/mastodon/src/main/res/color/m3_primary_overlay.xml new file mode 100644 index 000000000..38153edf5 --- /dev/null +++ b/mastodon/src/main/res/color/m3_primary_overlay.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_radiobutton_tint.xml b/mastodon/src/main/res/color/m3_radiobutton_tint.xml new file mode 100644 index 000000000..029457ae2 --- /dev/null +++ b/mastodon/src/main/res/color/m3_radiobutton_tint.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_button_m3_filled.xml b/mastodon/src/main/res/drawable/bg_button_m3_filled.xml new file mode 100644 index 000000000..8ba6277c9 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_button_m3_filled.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_button_m3_text.xml b/mastodon/src/main/res/drawable/bg_button_m3_text.xml new file mode 100644 index 000000000..a49879ef7 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_button_m3_text.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_m3_cancel.xml b/mastodon/src/main/res/drawable/ic_m3_cancel.xml new file mode 100644 index 000000000..258e402fb --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_m3_cancel.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_m3_search.xml b/mastodon/src/main/res/drawable/ic_m3_search.xml new file mode 100644 index 000000000..1b2a144a7 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_m3_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/rect_4dp.xml b/mastodon/src/main/res/drawable/rect_4dp.xml new file mode 100644 index 000000000..c44581d40 --- /dev/null +++ b/mastodon/src/main/res/drawable/rect_4dp.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/splash_logo.xml b/mastodon/src/main/res/drawable/splash_logo.xml index cb724a171..0486618e4 100644 --- a/mastodon/src/main/res/drawable/splash_logo.xml +++ b/mastodon/src/main/res/drawable/splash_logo.xml @@ -1,7 +1,7 @@ + android:fillColor="#000"/> + android:fillColor="#000"/> diff --git a/mastodon/src/main/res/drawable/white_circle.xml b/mastodon/src/main/res/drawable/white_circle.xml new file mode 100644 index 000000000..72bfc3def --- /dev/null +++ b/mastodon/src/main/res/drawable/white_circle.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/fragment_login.xml b/mastodon/src/main/res/layout/fragment_login.xml new file mode 100644 index 000000000..0abb69439 --- /dev/null +++ b/mastodon/src/main/res/layout/fragment_login.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + +