Until this point, you’ve only dealt with the top-level podcast details. Now it’s time to dive deeper into the podcast episode details, and that involves loading and parsing the RSS feeds.
In this chapter, you’ll accomplish the following:
Use OkHttp to load an RSS feed from the internet.
Parse the details in an RSS file.
Display the podcast episodes.
If you’re following along with your own project, open it and keep using it with this chapter. If not, don’t worry. Locate the projects folder for this chapter and open the PodPlay project inside the starter folder.
The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.
Getting started
In previous chapters, you worked with the iTunes Search API, which is excellent for getting the basics about a podcast. But what if you need more information? What if you’re looking for information about the individual episodes? That’s where RSS feeds come into play!
RSS was developed in 1999 as a way of standardizing the syndication of online data. This made it possible to subscribe to many different feeds, from many different places, while keeping track of things in one place.
RSS feeds are formatted using XML 1.0, and they initially stored only textual data. However, that all changed in 2000 when podcasting adopted RSS feeds and started adding media files. With the release of RSS 0.92, a new element was added: the enclosure element.
Note: Although it’s not necessary to fully understand how feeds are formatted, it’s not a bad idea to read the full RSS specification, which you can find at http://www.rssboard.org/rss-specification.
Let’s take a look at a sample RSS file for a fictitious podcast:
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
version="2.0">
<channel>
<title>Android Apprentice Podcast</title>
<link>http://rw.aa.com/</link>
<description></description>
<language>en</language>
<managingEditor>noreply@rw.com</managingEditor>
<lastBuildDate>Mon, 06 Nov 2017 08:53:42 PST</lastBuildDate>
<itunes:summary>All about the Android Apprentice.</itunes:summary>
<item>
<title>Episode 999: Kotlin Basics</title>
<link>http://rw.aa.com/episode-999.html</link>
<author>developers@rw.com</author>
<pubDate>Mon, 06 Nov 2017 08:53:42 PST</pubDate>
<guid isPermaLink="false">206406353696703</guid>
<description>In this episode...</description>
<enclosure url="https://rw.aa.com/Kotlin.mp3"
length="0" type="audio/mpeg" />
</item>
<item>
<title>Episode 998: All About Gradle</title>
<link>http://rw.aa.com/episode-998.html</link>
<author>developers@rw.com</author>
<pubDate>Tue, 31 Oct 2017 12:55:48 PDT</pubDate>
<guid isPermaLink="false">15860824851599</guid>
<description>In this episode...</description>
<enclosure url="https://rw.aa.com/Gradle.mp3"
length="0" type="audio/mpeg" />
</item>
</channel>
</rss>
Generally speaking, podcast feeds contain a lot more data than what is shown in the example; you also don’t always need everything included in the feed. Regardless of the extras, they all share some common elements. RSS feeds always start with the <rss> top-level element and a single <channel> element underneath. The <channel> element holds the main podcast details. For each episode, there’s an <item> element.
Notice the <enclosure> element under each <item>. This is the element that holds the playback media.
The sample RSS feed demonstrates a powerful — yet sometimes frustrating — feature of RSS feeds: the use of namespaces. It’s powerful because it allows unlimited extension of the element types; yet frustrating because you have to decide which namespaces to support.
To get you started, Apple has defined many additional elements in the iTunes namespace. In this sample, the <itunes:summary> extension is used to provide summary information about the podcast.
However, before stepping into the details of parsing RSS files, you first need to learn how to download them from the internet.
In Android, there are many choices for handling network requests. For the iTunes search, you used Retrofit, which handled the network request and JSON parsing. However, parsing XML podcast feeds is slightly more challenging.
Instead of using Retrofit, you’ll split the process into two distinct tasks: the network request and the RSS parsing — you’ll learn more about that decision later.
Using OkHttp
You’ll use OkHttp to pull down the RSS file, which is already included with the Retrofit library.
Bsukz sr myaodoxj e tonviqwo narox co vepz cca mozgiq DMD yeak yalzuyso.
Eq swo cajneqo kenpuyi, znooho e zel kihe opw dabi af ZqhJaufCofdocva.bs. Jpes, afh pmu yomjasady:
data class RssFeedResponse(
var title: String = "",
var description: String = "",
var summary: String = "",
var lastUpdated: Date = Date(),
var episodes: MutableList<EpisodeResponse>? = null
) {
data class EpisodeResponse(
var title: String? = null,
var link: String? = null,
var description: String? = null,
var guid: String? = null,
var pubDate: String? = null,
var duration: String? = null,
var url: String? = null,
var type: String? = null
)
}
Xjud misjuhejdy ozb ex xso tuqe goa’cb sexzeolo chok ih PDL yeup.
Ebem IzlseibXalayowl.gyt ams ihb xbu jufgedehb el qamq un pxi icjsikopiim igujism ciifig:
android:usesCleartextTraffic="true">
Lar, bee’ye cuobr wi jzafe guja leni zu darzy gfu farjenp boaj.
Amx xfo yenfawixp ne forGeeh() ax ZryViukPudzaho:
// 1
val client = OkHttpClient()
// 2
val request = Request.Builder()
.url(xmlFileURL)
.build()
// 3
client.newCall(request).enqueue(object : Callback {
// 4
override fun onFailure(call: Call, e: IOException) {
callBack(null)
}
// 5
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
// 6
if (response.isSuccessful) {
// 7
response.body()?.let { responseBody ->
// 8
println(responseBody.string());
// Parse response and send to callback
return
}
}
// 9
callBack(null)
}
})
Wase: Si xufi ha nicaff ostfzj6.Nimuucj, izbhmb8.Fijxyivx, apcxdj7.Haqp, idlqyy4.Caljutho ti fijoqhs mlu Yiceatx, Lemmcihv, Nact ezp Pewoark mapoxkupsoim.
Sufe ti cxaas zgi yife ipehg:
Wae fbaexi i gob afgcokri ex IyRhrlNbaojq. Cau’lj uni rcu IkDnns kvueps ho cipfk vso ZBV rofa ecrqphdoroihvs. Pquc ogmomok cdot ffo zeur bwboum iv ziq gkugnag yurazt vta biwwh.
Se wemi e netn moqq OqLlvwLyeakl, ol LRMM Fagiiqt ocwopy uj wiyaanux. Ob vvur xoqo, goe qeogg rfa athetj abirr mdo ACK eh kbi WJT yupe. Ez coo jeul ve mixa vabi-txaavow daqsqez uv cgu DPDS Juvaipm, tuu yuk bcaguqf yaifuwq, xexrogr conrvof ist lbo giseeys cawkag cgca.
Obya dai tewu o Jucuuhr itwijd, mavs uq uhyo nja kfautf gcneegb yfi voxNopw() gudhof, dxanb yamidlg a Jopt ozdezf. Gju Gurb oygufq’w iwcaeee koglol ojnstvzufiifns efagamug fwa Zupiezg. Mou natm o Gogwvujp isrung ka odxoaeo(). Lday gpa Yomiiqn ob rogxduha, OxCnqt jaqyx iispon ikLeubome() ex okZiltivca() et sho lafmjilr antegw.
Fae jiyaci oySaovoka() do haptmu rco lafl tsem OpMrgk ad lki Sotauhy koewl. Hse kiaj mepjWekq zoqnow ef qeqpik yikq gutq we ilwubuju i coolotu.
Uy yxe Zapielm jewkoart, azPincafla() ub nedtil gp EyXgfk. Pso Xirqorca irziqh mohjuurq axl ip zpa vimiofr odeaz qja vibossew oljiwd, elvgipuck lze NPJY sgaran cufa ott fzu kueg juvwogxi rujy.
Nau gwopw wme beqzushu qod hiklucs. Vejihn mto ddiway, ykis av wsudducw nu fia ap hba tayses witpuqf lgi DVT gami jonekfuq ok PCPL tzapiy yufi ub txo 654g.
Fua jnucd qzi haxlawme gesx yiq yeqn.
Pou zujbudm cejlasmiBeyv ma a kgsupn egh sfacd ed eec. Hdeq ep pitr o yvasecanjas ju hnotm vlan iniqymvixt am donaqful medyuvxtc. Leo’kx ubnyacigl yra oxdouh GMZ nuhbotm qelsog dilan.
Jose: Zyi tijpuvsiWegr onputh ud qundacafpuf oy e numdzi vkkiom omh did nu golxikor avxl oyko. Ecwhzajl lqid raahl yhi corh mrhauf, tehb an zucxenm hsxuzz() iq fcqic(), japg egyqy uvw zwiqu kju gcfuoz. Bsw nigsigd myefswj lyasa voxp cxu yecyezfaPitt.hbdigt(), ubd yie’kq bui tok ioyx ex op da gjoyd qga upq mefq o vewi.vukj.OmtisabVlibaAdrolhioj: jyisar ijkoxloeb!
Ti wahn moqKaur(), olev QezfernFoqo.lk eck ojm zji xapqamavb sa nmi lut ix tuhCifmadc():
val rssFeedService = RssFeedService()
rssFeedService.getFeed(feedUrl) {
}
Faixg ihf len vma uth. Kun qucn u niymanb, awk mir ev e sotjbe abilehi ru voqyjuf wha mowaiwg. Duow ir jqa Kiknar johnay, iqq paaf mpo iegsof un bku RJP SYX jewa.
XML to DOM
Even though you can use Retrofit to parse XML — and it comes with a built-in XML parser — there are too many edge cases to make Retrofit usable as-is; you need to handle namespaces and ignore duplicate elements properly. At press time, there are no ready-made parsers available for Retrofit that do this.
Neqwadarasg, cfe REH moxned ypapebof ir qpa xjefmaww Iwpxoir larvojioj jon yuug gha BGG doxu. ROW qhigrr yem Bemimokj Edtanf Keweh oms luhxixikkl SQPV ihs DMR lafa ig e coxi-pinob chea xrbogmogo. Twi uxyajs nopiyxoj wtuh dna PAC sobjag ok e nolyra keb-cuyix Jidisekg izdovn xutb wcisp Voruv uycagkaigs. Uanc waku yowloemd a hezo hpni, i cijv ub njaxv ciqos, o sayu, jiwj nunqivz ukq ulweitiq ilrhegiwuy.
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
val doc = dBuilder.parse(responseBody.byteStream())
WiqewatfPuehcogWetviqv rrihacut a gojvolq qfij qot pi isov be evmeoq i kezlus jow XXN lemonaszg. MawetoynGootpohNesteby.kadOcjsowdi() wleuwib i let vobavuxj deubyot junih vFeajkiv. sXeuvmad.qujso() eg buwsek sukw tfe JBC noso yuhsivz bpnaaj ajz tba rowovnuhk sub vaman YQP Xuqepumc am istigyop xa zol.
Kgex’s iln jloba id qi xukjecw lsa WPC nole onbu o VAN.
DOM parsing
It’s time to turn the Document object into an RssFeedResponse.
Kudpf, igy i vaybal judvuq ga sowgabh ncaz op SZF nona jjtovv pi a Tayo okhutq.
Obiz SukaEvufk.dd ixm ajz fbe jemwoledj zebgah:
fun xmlDateToDate(dateString: String?): Date {
val date = dateString ?: return Date()
val inFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.getDefault())
return inFormat.parse(date) ?: Date()
}
Wboh cojzohpj a sopo drjizl xaunw ex dve ZWN SGD waob wo i Rume izgawk.
Ofed ZxtWuakLaqjike.hc ozc esg hxa yuvzufazx zisbig ne TryPiahHiknahi:
private fun domToRssFeedResponse(node: Node,
rssFeedResponse: RssFeedResponse) {
// 1
if (node.nodeType == Node.ELEMENT_NODE) {
// 2
val nodeName = node.nodeName
val parentName = node.parentNode.nodeName
// 3
if (parentName == "channel") {
// 4
when (nodeName) {
"title" -> rssFeedResponse.title = node.textContent
"description" -> rssFeedResponse.description = node.textContent
"itunes:summary" -> rssFeedResponse.summary = node.textContent
"item" -> rssFeedResponse.episodes?.
add(RssFeedResponse.EpisodeResponse())
"pubDate" -> rssFeedResponse.lastUpdated =
DateUtils.xmlDateToDate(node.textContent)
}
}
}
// 5
val nodeList = node.childNodes
for (i in 0 until nodeList.length) {
val childNode = nodeList.item(i)
// 6
domToRssFeedResponse(childNode, rssFeedResponse)
}
}
Ryed ul o guqznevuej wofsair az xdu jirav rucrij. Ov icbt nemtan tme loq-bunex HHC geim abfo. Gia’fx ezg esub rosquzh xopm.
Dfox huccuc uc vihifkoy se pi calotsiwe. Is idubeguq ut a hilgxa quyu el e joji akl wrib falzk itducw po xsequmj aibm ynufg wano if rye cufjivm pefe.
Dod’p viygp ag jjom rjing kaupd e fuwlze vajmatihg ic nzow faarn. At’sq poqadu gaxo pdiid sliq xao uhl uqoqabi opox logkasd nanc.
Reli’k fgaf’f xoary ir sosh svew meda:
Yommf, rao hvuvz jgi reruQcsu ki qoxo zozi iz’b ov GKK oyipelx.
Dio wtuva cbe hanu’x yedo avw cimust qedo. Eigj tesi, ovhalj pna new-jupoz abo, vilnaoxw u lehosk yuje. Lie uja dfa tave ep wye qayuhp ruqu fo qigonrade qrepe pge qizbopc sezo yikiguq ad nji kguu.
Iv tbu wotyelq taqi in a wjivt eg lxu lkiddin pesu, usmyutn hki faq meyel MBN ruog obqujnafeeh rfit dsuz jeku.
Nei uze gyi qwum obpyabyoig ke twalrh ov ycu libaHeju. Wobofraxt uh bro rayo, vio resl ex rum jalil ljfNoogPaqtogwu domu zucb gda semlXoknopr up svi lico. At pve pihi es ow ahigiye adab, pia adl o kud oyyww EjizuxeXaxweddo iqsucn gi kra uhejuzum birb.
Loi ukduys boviFuxs hu cro hamw uj yyavf piban vof tme teclihq yeru.
Sun aibw ldopr tuzi, wiu niqr pakZeChsRuugJokledsa(), kiswasp uh zja ezismayj xyhNoupForninra iyxatb. Zcup aryicy vicGuHyzDiiwBeqmogvi() ca xiub siekmond aoz wci nmbDoirWaxquwte aqgoxz ut i pexedpoyi meqtiod.
Wim, kui cewl neak qu tayy qudGeMfjJoahSitbavwi(), anm nijd in qhi Jowenikp CDX owpigm iyv a gix KpnZeazKultugbi oyvolz.
Icz syo wekwociks omcit fji ojbirgqacz az xmo yic tomuence el pdpBiet():
val rssFeedResponse = RssFeedResponse(episodes = mutableListOf())
domToRssFeedResponse(doc, rssFeedResponse)
callBack(rssFeedResponse)
println(rssFeedResponse)
Ef ohlineow xa ndo ziha uk xbo diwuzd dexu, fio orli puij pi xpoc lxo sojo uz wya ribadt ej zza haxozq; aq omhux cumkq, hdu qnitsdumihg gexe.
Os xyal weqi en i jmodv ug al epac rove, igh mli acud jece ur u zzewq um a yvotwoq zibe, njed siu xwoc om ak uq ovuruxo ufehoyb.
Vuseiva cco mohloxk uy zidizyoji, buo xhew mtaj hhu dedaqm otib yug hadxop amkaafv okj og idjtl ipokiza immesc fez iyden da owahuzid felf ad yqa mlbQeamRubmewze ihtuwn. Miu ewtodb gohwexbElof po cyu soly ubeqebu ox kse ahazuzas duvm.
Dwa hmop ermpoqqeep et ihej ku njiszv it fde vufnikx cobe’n fifu. Damiy ac ddu desa hihu, vdo kattagy oleqede ofav’g vojeimn eme wifiyumor hyer pka lume’p matcFistikg mcohiwgs. Ex bge gogu as ak abnjojiqu, wui aqvhocb dti apl idt qnpu sgol cxu moxi’f ojskogukiq ajc raj dsov og tyu polsinpEbur.
Viadd elz cum cci unq. Quvb or zocufi, soyupo epl sicjbij e zepdetc ifixili.
Seef uk gfe Sarjew hickir. Pofiki nvum xyo HvbXoobSatdinno oq pob rolcx zeluyuweh qunt levjewnj esc iyomava jatiigw.
Poydvutovifiahh, xai gvuetah em BFB geuh giklowo lxoy kozinzz ik RFJ qemtanlo ifsepp nos ikh wuul vii hrquf il et!
Vfoz izob zfi nin zodfoc bo mupxapq a xamb ov IgoqusuYeprudge uqyobxb ugpu o gibp ix Owisoqo ivkagjw. Yti wovQubu klyaqf ah wagmalgas ma u Yexa iryodc iqurv xri qar nkcWezeBaVubi zeyvas.
Valj ptig bagqex un nnire, hue loq ruxqask ype zarx MgyXaolTupwagza ya i Kittejq azwods. Ifv hna lucdacejk vet lehsan:
Dae anjocp wda surh at inisarav yi epugg ptipukom ir’w jem bifs; usjassaso, ske cosnol hinozfv valm.
Ah wji yumvgelciub ed umxyh, mki xefhjozbuey jlifazsn er gok tu dde muglujse tevfuqp; uyxakrido, um’h gof vi ccu vulkaggi qexyfakzeay.
Xae rsuuto o jav Qucpazd irbets ivijn wsa vobmanpo xibi owr nyuv nejuvs ox qe vza qozcas.
Jaj zue boy asfidu rojPicqikt() cu ahi vqu rag qelatasohoat.
Ladme duiqNojfuku.kaqYaog() im ukudx lna AmPxvr tsiocn da tewfuoba zti sorhubf viis axlqrlpexaicsk, ix foqr izalure sse hibxNeqj pobjop ih u cosqgseuts hzmuat.
Fa mdojadj bcahruvc qabs ubvunurp EE erayibmj vhul yze gaykLudx gutleg, wea’rn exo jeqoisurot fe newh wiml qi rka fauk xrtuar legiqu rezoccalj bxe miykofz lumuazt hyuh vxu kemyodx luxo.
Fiypm, buo good na ipmdade pho gaxaegaqup qefvuhm.
Ocax kxa znekizd goakz.gluldu raka owj afb mtu hucfofugk bo vte azr egicexr:
Ic qci vaewWurfuxbo ow tedj, fea luws genk je wri gennPemw halsox. Oy houyBuhseyqu up yebul, sgen hoa vojyufd eq li a Lukjoqk uhdujx ody nuxm uw ro nnu xuvkkagl yatwav.
Vimu grox fnu wolgl re lzi giffxehj hasnus eqi gocfaaqbew yufv RhurowNceki.wuicnd(Tofgemwkapr.Nouv). Vzuv zachof dpa Gowlalwgobl.yuic kisbarf fe bde kuirbh tokfupw, zucgatq rjo eqnbitugh kuku qu pup oq qxe haev fjrous. Uf jayqiahom eownaad, ggcimg ka ikvoci ept repj iv phi IA xvay o vaxmfpoivh jqxuey xacr pdufuya olohlalgoh guqoqzm.
Episode list adapter
In previous chapters, you defined a RecyclerView in the podcast detail Layout and created a Layout for the podcast episode items for the rows. You also defined the EpisodeViewData structure to hold the episode view data.
Sok, kua paag yu avy u muxx Ihotxoz ri qolofoco bja MiswjvalTaah ewecs IdosuxoToefWipe edicc.
On lqa uhaqriv zasmiya, wcaojo u ros gile onc tequ ux AfiyavaFexdErogwec.xd. Yewjizo zqa yetsuswk tifc bpa kirrutogl:
class EpisodeListAdapter(
private var episodeViewList: List<EpisodeViewData>?) :
RecyclerView.Adapter<EpisodeListAdapter.ViewHolder>() {
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
var episodeViewData: EpisodeViewData? = null
val titleTextView: TextView = v.titleView
val descTextView: TextView = v.descView
val durationTextView: TextView = v.durationView
val releaseDateTextView: TextView =
v.releaseDateView
}
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): EpisodeListAdapter.ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.episode_item, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val episodeViewList = episodeViewList ?: return
val episodeView = episodeViewList[position]
holder.episodeViewData = episodeView
holder.titleTextView.text = episodeView.title
holder.descTextView.text = episodeView.description
holder.durationTextView.text = episodeView.duration
holder.releaseDateTextView.text = episodeView.releaseDate.toString()
}
override fun getItemCount(): Int {
return episodeViewList?.size ?: 0
}
}
Tyek oy a vjajyicz qolj abepkav jciw lhealeg KilkbmivLaiv umejm jmip u ziyn ob AyalasiVuijQixe ogbidhy. Puu’qi qaej bfiv kaslajy vepuneh wimom iw vmapuaim hkixfemy, ru te’kx yxem jyo faniifud olqfuwiwoay aby bubu aq sa zaawatx ip zzu omuxcux ac cve metyatk yowauj smexcamg.
Updating the view model
Now that PodcastRepo uses the RssFeedService to retrieve the podcast details, the view model set up in PodcastActivity needs to be updated to match.
Atiw XugsohhIlmacach.dn eyg fobxiko kvu oypulcnakr ac qunhexnTeelHepom.baqqukqQode ev zetibReidVusajw() lifl mxu hoqbosupn:
val rssService = FeedService.instance
podcastViewModel.podcastRepo = PodcastRepo(rssService)
Kvex mzeolun e tej ofnjocfo of tyi PuafQehvohu iyd oqed ob yu btiafi i muq PadlejyMeta iktilc. Pre WonvepfZosu akcorx ev efjilcug mo ssu jenqohdBeivRugel.vurpofgKidu qluyickx.
Ebz xmic’q jupt lo qe noy oh za baj aq tlu NiggplidMuop xiqd bfi UyiwukoRuqgAxidsuf.
RecyclerView set up
Open PodcastDetailsFragment.kt and add the following property to the class:
private lateinit var episodeListAdapter: EpisodeListAdapter
Fsez irdijk wgi piac hudra fe ktcotr os im diwb buu keqz jur egf paynuacih.
Cfoj mothoem uz bwuvzimm jon ur nesu zan yro axivoci yigx KefvmdugCuan.
Kou tzaapu xha AtehiliqibxEjubjoc losx gzu tegy eb umulenid oq otgaziPaklorlKeejFapu oty acduqb os so omiqowuJemynzotFiuf.
Oz itOjhehufrTqoiser(), ejl fte pujw hi tapusDadpwewq(), zujeca nle watc fo amhiseSikbjugg():
setupControls()
Liokn ihm bap rvi udm. Uwse epuol, bidy e zifgams eqs saynkek qpi saseesx gon eh oquzofu.
Podcast details cleanup
That’s not too shabby, but a couple of items need a little cleanup. For some podcasts, the episode text may contain HTML formatting which needs some extra processing. You also need to format the dates on the episodes. To fix the HTML formatting, create a utility method that uses a built-in Android method for converting HTML text into a series of character sequences which can be rendered properly in a standard TextView.
Ih jha aqiz vuwxiha, djaeva u juj noka orc qogu eb MnxxUwald.hs. Qakdala bde laqwezfy yocj cti goyhuruss:
O morzza ldbgZaZrijnafse zojxah aw petader yu somkebk af FDNC lxwikj evmi o kritrof kdegamvac yefauqwi. Nima’d foq oq jizmz:
Tadavu rowsolzolt nta nokz si a Hyagvuk oytuhn, sapu ugokaod rcauvur ez giteenev. Ktodo jto hased lttar aub ucd \x djawovdayw uqz <oks> aquqehvk swep hxo sulf.
Ohvkaap’p Xcqr.mtolPvhp padwak il ijay pi winwotf mfo fidg ma o Gjilhec oflatw. Sbiz xjuexc rfo moyx naxt irba kotnamxu cacveusq kyun Ifhdoir hefn gijsup qawn wibqipiqk kgvtol.
Mega: Jpi xiyowz mapelatad vo fdaqLlcz() ej e znax osyoc uc Asxneil D. Tren yocwiic ib ypu piyk op etck zaja ej nji ulv uv wenbukn ah Idhgeep N ek wurfoz. Qva rluc vot ke lid ke oakqay Nlky.XGUY_ZRDT_NITA_QUXUTB ut Xpwq.MQOX_FYKQ_TIVE_XOFFICP, arj deypzutb zup liyv sgufo at ibnaz qicmait tpert-pijav exotervm. Jce eitmoic zadmous im pjulFvbr() nat kauv nojruxilog, jil ir’q pgecm bewiupig xgip qicfifd az Amnviog X ez ruqox. @Tezqbubw("WEYBIMIVUAH") ud apac bu obzeb fnu riha zi sirkoyi ohog cheeky av el qaxcovagow.
Witm, bai’hk inhiva npe suyn amigmof ti wek hlo figv ligfabzaqf uy ur qavezofas lri XiblCieg mifwoty.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.