refactor: Use the PachliError type for ApiError (#739)

In the previous code `PachliError` could correctly chain errors and
generate error messages, `ApiError` didn't, which is why there was the
temporary `ApiError.fmt()` extension function.

Rewrite `ApiError` to implement `PachliError` so it gets these benefits
and to reduce the number of different error-handling mechanisms in the
code.

Main changes:

- `PachliError` is now an interface so it can be extended by other
  error interfaces.
- All the `ApiError` subclasses implement `PachliError`, and can
  specify the error string and interpolated variables at the point of
  declaration.
- Update `ListsRepository` and `ServerRepository` to return
  `PachliError` subclasses.
This commit is contained in:
Nik Clayton 2024-06-12 10:22:27 +02:00 committed by GitHub
parent 7bf8c382e1
commit efd1c8e556
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 411 additions and 327 deletions

View File

@ -124,7 +124,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="133" line="132"
column="5"/> column="5"/>
</issue> </issue>
@ -168,7 +168,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-sl/strings.xml" file="src/main/res/values-sl/strings.xml"
line="180" line="179"
column="5"/> column="5"/>
</issue> </issue>
@ -179,7 +179,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="194" line="193"
column="5"/> column="5"/>
</issue> </issue>
@ -190,7 +190,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-bn-rIN/strings.xml" file="src/main/res/values-bn-rIN/strings.xml"
line="201" line="200"
column="5"/> column="5"/>
</issue> </issue>
@ -212,7 +212,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-sl/strings.xml" file="src/main/res/values-sl/strings.xml"
line="214" line="213"
column="5"/> column="5"/>
</issue> </issue>
@ -223,7 +223,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="236" line="235"
column="5"/> column="5"/>
</issue> </issue>
@ -234,7 +234,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="237" line="236"
column="5"/> column="5"/>
</issue> </issue>
@ -245,7 +245,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-bn-rIN/strings.xml" file="src/main/res/values-bn-rIN/strings.xml"
line="242" line="241"
column="5"/> column="5"/>
</issue> </issue>
@ -267,7 +267,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-hi/strings.xml" file="src/main/res/values-hi/strings.xml"
line="270" line="269"
column="5"/> column="5"/>
</issue> </issue>
@ -278,7 +278,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="273" line="272"
column="5"/> column="5"/>
</issue> </issue>
@ -289,21 +289,21 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="276"
column="5"/>
</issue>
<issue
id="MissingQuantity"
message="For locale &quot;cs&quot; (Czech) the following quantity should also be defined: `many` (e.g. &quot;10.0 dne&quot;)"
errorLine1=" &lt;plurals name=&quot;favs&quot;>"
errorLine2=" ^">
<location
file="src/main/res/values-cs/strings.xml"
line="277" line="277"
column="5"/> column="5"/>
</issue> </issue>
<issue
id="MissingQuantity"
message="For locale &quot;cs&quot; (Czech) the following quantity should also be defined: `many` (e.g. &quot;10.0 dne&quot;)"
errorLine1=" &lt;plurals name=&quot;favs&quot;>"
errorLine2=" ^">
<location
file="src/main/res/values-cs/strings.xml"
line="278"
column="5"/>
</issue>
<issue <issue
id="MissingQuantity" id="MissingQuantity"
message="For locale &quot;cs&quot; (Czech) the following quantity should also be defined: `many` (e.g. &quot;10.0 dne&quot;)" message="For locale &quot;cs&quot; (Czech) the following quantity should also be defined: `many` (e.g. &quot;10.0 dne&quot;)"
@ -311,7 +311,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="283" line="282"
column="5"/> column="5"/>
</issue> </issue>
@ -322,7 +322,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="302" line="301"
column="5"/> column="5"/>
</issue> </issue>
@ -333,7 +333,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="313" line="312"
column="5"/> column="5"/>
</issue> </issue>
@ -344,7 +344,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="327" line="326"
column="5"/> column="5"/>
</issue> </issue>
@ -355,7 +355,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="332" line="331"
column="5"/> column="5"/>
</issue> </issue>
@ -366,7 +366,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="337" line="336"
column="5"/> column="5"/>
</issue> </issue>
@ -377,7 +377,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-bg/strings.xml" file="src/main/res/values-bg/strings.xml"
line="369" line="368"
column="5"/> column="5"/>
</issue> </issue>
@ -388,7 +388,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="369" line="368"
column="5"/> column="5"/>
</issue> </issue>
@ -399,7 +399,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="397" line="396"
column="5"/> column="5"/>
</issue> </issue>
@ -410,7 +410,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="401" line="400"
column="5"/> column="5"/>
</issue> </issue>
@ -421,7 +421,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="405" line="404"
column="5"/> column="5"/>
</issue> </issue>
@ -432,7 +432,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="409" line="408"
column="5"/> column="5"/>
</issue> </issue>
@ -443,7 +443,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="417" line="416"
column="5"/> column="5"/>
</issue> </issue>
@ -454,7 +454,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="422" line="421"
column="5"/> column="5"/>
</issue> </issue>
@ -465,7 +465,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-ca/strings.xml" file="src/main/res/values-ca/strings.xml"
line="425" line="424"
column="5"/> column="5"/>
</issue> </issue>
@ -476,7 +476,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-cs/strings.xml" file="src/main/res/values-cs/strings.xml"
line="427" line="426"
column="5"/> column="5"/>
</issue> </issue>
@ -509,7 +509,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="34" line="33"
column="44"/> column="44"/>
</issue> </issue>
@ -520,7 +520,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-tr/strings.xml" file="src/main/res/values-tr/strings.xml"
line="40" line="39"
column="50"/> column="50"/>
</issue> </issue>
@ -531,7 +531,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-tr/strings.xml" file="src/main/res/values-tr/strings.xml"
line="40" line="39"
column="46"/> column="46"/>
</issue> </issue>
@ -542,7 +542,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="71" line="70"
column="38"/> column="38"/>
</issue> </issue>
@ -553,7 +553,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="73" line="72"
column="46"/> column="46"/>
</issue> </issue>
@ -564,7 +564,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="79" line="78"
column="44"/> column="44"/>
</issue> </issue>
@ -575,7 +575,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="102" line="101"
column="45"/> column="45"/>
</issue> </issue>
@ -586,7 +586,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="106" line="105"
column="44"/> column="44"/>
</issue> </issue>
@ -597,7 +597,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="107" line="106"
column="49"/> column="49"/>
</issue> </issue>
@ -608,7 +608,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="110" line="109"
column="38"/> column="38"/>
</issue> </issue>
@ -619,7 +619,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="125" line="124"
column="70"/> column="70"/>
</issue> </issue>
@ -630,7 +630,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="158" line="157"
column="78"/> column="78"/>
</issue> </issue>
@ -641,7 +641,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="164" line="163"
column="65"/> column="65"/>
</issue> </issue>
@ -652,7 +652,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="200" line="199"
column="32"/> column="32"/>
</issue> </issue>
@ -663,7 +663,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="268" line="267"
column="43"/> column="43"/>
</issue> </issue>
@ -674,7 +674,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="396" line="395"
column="86"/> column="86"/>
</issue> </issue>
@ -685,7 +685,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-tr/strings.xml" file="src/main/res/values-tr/strings.xml"
line="504" line="503"
column="294"/> column="294"/>
</issue> </issue>
@ -696,7 +696,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/res/values-nb-rNO/strings.xml" file="src/main/res/values-nb-rNO/strings.xml"
line="526" line="525"
column="51"/> column="51"/>
</issue> </issue>
@ -729,7 +729,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="101" line="100"
column="5"/> column="5"/>
</issue> </issue>
@ -740,7 +740,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="288" line="287"
column="5"/> column="5"/>
</issue> </issue>
@ -751,7 +751,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="339" line="338"
column="5"/> column="5"/>
</issue> </issue>
@ -762,7 +762,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="458" line="457"
column="5"/> column="5"/>
</issue> </issue>
@ -773,7 +773,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="633" line="632"
column="5"/> column="5"/>
</issue> </issue>
@ -1290,7 +1290,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="76" line="75"
column="13"/> column="13"/>
</issue> </issue>
@ -1301,7 +1301,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="82" line="81"
column="13"/> column="13"/>
</issue> </issue>
@ -1312,7 +1312,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="103" line="102"
column="13"/> column="13"/>
</issue> </issue>
@ -1323,7 +1323,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="111" line="110"
column="13"/> column="13"/>
</issue> </issue>
@ -1334,7 +1334,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="145" line="144"
column="13"/> column="13"/>
</issue> </issue>
@ -1345,7 +1345,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="204" line="203"
column="13"/> column="13"/>
</issue> </issue>
@ -1356,7 +1356,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="225" line="224"
column="13"/> column="13"/>
</issue> </issue>
@ -1367,7 +1367,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="226" line="225"
column="13"/> column="13"/>
</issue> </issue>
@ -1378,7 +1378,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="249" line="248"
column="13"/> column="13"/>
</issue> </issue>
@ -1389,7 +1389,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="278" line="277"
column="13"/> column="13"/>
</issue> </issue>
@ -1400,7 +1400,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="349" line="348"
column="13"/> column="13"/>
</issue> </issue>
@ -1411,7 +1411,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="389" line="388"
column="13"/> column="13"/>
</issue> </issue>
@ -1422,7 +1422,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="425" line="424"
column="13"/> column="13"/>
</issue> </issue>
@ -1433,7 +1433,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="428" line="427"
column="13"/> column="13"/>
</issue> </issue>
@ -1444,7 +1444,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="429" line="428"
column="13"/> column="13"/>
</issue> </issue>
@ -1455,7 +1455,7 @@
errorLine2=" ~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="430" line="429"
column="13"/> column="13"/>
</issue> </issue>
@ -1466,7 +1466,7 @@
errorLine2=" ~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="431" line="430"
column="13"/> column="13"/>
</issue> </issue>
@ -1477,7 +1477,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="432" line="431"
column="13"/> column="13"/>
</issue> </issue>
@ -1488,7 +1488,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="444" line="443"
column="13"/> column="13"/>
</issue> </issue>
@ -1499,7 +1499,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="445" line="444"
column="13"/> column="13"/>
</issue> </issue>
@ -1510,7 +1510,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="496" line="495"
column="13"/> column="13"/>
</issue> </issue>
@ -1521,7 +1521,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="497" line="496"
column="13"/> column="13"/>
</issue> </issue>
@ -1532,7 +1532,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="508" line="507"
column="13"/> column="13"/>
</issue> </issue>
@ -1543,7 +1543,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="509" line="508"
column="13"/> column="13"/>
</issue> </issue>
@ -1554,7 +1554,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="510" line="509"
column="13"/> column="13"/>
</issue> </issue>
@ -1565,7 +1565,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="512" line="511"
column="13"/> column="13"/>
</issue> </issue>
@ -1576,7 +1576,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="550" line="549"
column="13"/> column="13"/>
</issue> </issue>
@ -1587,7 +1587,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="591" line="590"
column="13"/> column="13"/>
</issue> </issue>
@ -1598,7 +1598,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="597" line="596"
column="13"/> column="13"/>
</issue> </issue>
@ -1609,7 +1609,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="625" line="624"
column="13"/> column="13"/>
</issue> </issue>
@ -1620,7 +1620,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="639" line="638"
column="13"/> column="13"/>
</issue> </issue>
@ -1631,7 +1631,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/res/values/strings.xml" file="src/main/res/values/strings.xml"
line="652" line="651"
column="13"/> column="13"/>
</issue> </issue>

View File

@ -65,7 +65,6 @@ import java.util.regex.Pattern
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
class TabPreferenceActivity : BaseActivity(), ItemInteractionListener { class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
@ -332,7 +331,6 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
selectListBinding.progressBar.hide() selectListBinding.progressBar.hide()
dialog.dismiss() dialog.dismiss()
Snackbar.make(binding.root, R.string.error_list_load, Snackbar.LENGTH_LONG).show() Snackbar.make(binding.root, R.string.error_list_load, Snackbar.LENGTH_LONG).show()
Timber.w(it.throwable, "failed to load lists")
} }
} }
} }

View File

@ -129,7 +129,7 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
val msg = getString( val msg = getString(
R.string.server_repository_error, R.string.server_repository_error,
accountManager.activeAccount!!.domain, accountManager.activeAccount!!.domain,
it.msg(requireContext()), it.fmt(requireContext()),
) )
Timber.e(msg) Timber.e(msg)
try { try {

View File

@ -19,36 +19,41 @@ package app.pachli.core.common
import android.content.Context import android.content.Context
import androidx.annotation.StringRes import androidx.annotation.StringRes
import app.pachli.core.common.string.unicodeWrap
/** /**
* Base class for errors throughout the app. * Interface for errors throughout the app.
* *
* Derive new error class hierarchies for different components using a sealed * Derive new error class hierarchies for different components using a sealed
* class hierarchy like so: * class hierarchy like so:
* *
* ```kotlin * ```kotlin
* sealed class Error( * sealed class Error(
* @StringRes resourceId: Int, * @StringRes override val resourceId: Int,
* vararg formatArgs: String, * override val formatArgs: Array<out String>,
* source: PachliError? = null, * cause: PachliError? = null,
* ) : PachliError(resourceId, *formatArgs, source = source) { * ) : PachliError {
* data object SomeProblem : Error(R.string.error_some_problem) * data object SomeProblem : Error(R.string.error_some_problem)
* data class OutOfRange(val input: Int) : Error( * data class OutOfRange(val input: Int) : Error(
* R.string.error_out_of_range * R.string.error_out_of_range, // "Value %1$d is out of range"
* input, * input,
* ) * )
* data class Fetch(val url: String, val e: PachliError) : Error( * data class Fetch(val url: String, val cause: PachliError) : Error(
* R.string.error_fetch, * R.string.error_fetch, // "Could not fetch %1$s: %2$s"
* url, * url,
* source = e, * cause = cause,
* ) * )
* } * }
* ``` * ```
* *
* In this example `SomeProblem` represents an error with no additional context, * In this example `SomeProblem` represents an error with no additional context.
* `OtherProblem` is an error relating to a URL and the URL will be included in *
* the error message, and `WrappedError` represents an error that wraps another * `OutOfRange` is an error relating to a single value with no underlying cause.
* error that was the actual cause. * The value (`input`) will be inserted in the string at `%1$s`.
*
* `Fetch` is an error relating to a URL with an underlying cause. The URL will be
* included in the error message at `%1$s`, and the string representation of the
* cause will be included at `%2$s`.
* *
* Possible string resources for those errors would be: * Possible string resources for those errors would be:
* *
@ -57,23 +62,27 @@ import androidx.annotation.StringRes
* <string name="error_out_of_range">Value %1$d is out of range</string> * <string name="error_out_of_range">Value %1$d is out of range</string>
* <string name="error_fetch">Could not fetch %1$s: %2$s</string> * <string name="error_fetch">Could not fetch %1$s: %2$s</string>
* ``` * ```
*
* In that last example the `url` parameter will be interpolated as the first
* placeholder and the error message from the error passed as the `source`
* parameter will be interpolated as the second placeholder.
*
* @property resourceId String resource ID for the error message
* @property formatArgs 0 or more arguments to interpolate in to the string resource
* @property source (optional) The underlying error that caused this error
*/ */
open class PachliError( interface PachliError {
@StringRes private val resourceId: Int, /** String resource ID for the error message. */
private vararg val formatArgs: String, @get:StringRes
val source: PachliError? = null, val resourceId: Int
) {
fun msg(context: Context): String { /** Arguments to be interpolated in to the string from [resourceId]. */
val formatArgs: Array<out String>
/**
* The cause of this error. If present the string representation of `cause`
* will be set as the last format argument when formatting [resourceId].
*/
val cause: PachliError?
/**
* @return A localised, unicode-wrapped error message for this error.
*/
fun fmt(context: Context): String {
val args = mutableListOf(*formatArgs) val args = mutableListOf(*formatArgs)
source?.let { args.add(it.msg(context)) } cause?.let { args.add(it.fmt(context)) }
return context.getString(resourceId, *args.toTypedArray()) return context.getString(resourceId, *args.toTypedArray()).unicodeWrap()
} }
} }

View File

@ -17,6 +17,7 @@
package app.pachli.core.data.repository package app.pachli.core.data.repository
import app.pachli.core.common.PachliError
import app.pachli.core.network.model.MastoList import app.pachli.core.network.model.MastoList
import app.pachli.core.network.model.TimelineAccount import app.pachli.core.network.model.TimelineAccount
import app.pachli.core.network.model.UserListRepliesPolicy import app.pachli.core.network.model.UserListRepliesPolicy
@ -36,26 +37,26 @@ interface HasListId {
} }
/** Errors that can be returned from this repository */ /** Errors that can be returned from this repository */
interface ListsError : ApiError { interface ListsError : PachliError {
@JvmInline @JvmInline
value class Create(private val error: ApiError) : ListsError, ApiError by error value class Create(private val error: ApiError) : ListsError, PachliError by error
@JvmInline @JvmInline
value class Retrieve(private val error: ApiError) : ListsError, ApiError by error value class Retrieve(private val error: ApiError) : ListsError, PachliError by error
@JvmInline @JvmInline
value class Update(private val error: ApiError) : ListsError, ApiError by error value class Update(private val error: ApiError) : ListsError, PachliError by error
@JvmInline @JvmInline
value class Delete(private val error: ApiError) : ListsError, ApiError by error value class Delete(private val error: ApiError) : ListsError, PachliError by error
data class GetListsWithAccount(val accountId: String, private val error: ApiError) : ListsError, ApiError by error data class GetListsWithAccount(val accountId: String, private val error: ApiError) : ListsError, PachliError by error
data class GetAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, ApiError by error data class GetAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, PachliError by error
data class AddAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, ApiError by error data class AddAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, PachliError by error
data class DeleteAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, ApiError by error data class DeleteAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, PachliError by error
} }
interface ListsRepository { interface ListsRepository {

View File

@ -119,13 +119,14 @@ class ServerRepository @Inject constructor(
} }
sealed class Error( sealed class Error(
@StringRes resourceId: Int, @StringRes override val resourceId: Int,
vararg formatArgs: String, override val formatArgs: Array<out String> = emptyArray<String>(),
source: PachliError? = null, override val cause: PachliError? = null,
) : PachliError(resourceId, *formatArgs, source = source) { ) : PachliError {
data class GetWellKnownNodeInfo(val throwable: Throwable) : Error( data class GetWellKnownNodeInfo(val throwable: Throwable) : Error(
R.string.server_repository_error_get_well_known_node_info, R.string.server_repository_error_get_well_known_node_info,
throwable.localizedMessage, throwable.localizedMessage?.let { arrayOf(it) }.orEmpty(),
) )
data object UnsupportedSchema : Error( data object UnsupportedSchema : Error(
@ -134,24 +135,23 @@ class ServerRepository @Inject constructor(
data class GetNodeInfo(val url: String, val throwable: Throwable) : Error( data class GetNodeInfo(val url: String, val throwable: Throwable) : Error(
R.string.server_repository_error_get_node_info, R.string.server_repository_error_get_node_info,
url, arrayOf(url, throwable.localizedMessage ?: ""),
throwable.localizedMessage,
) )
data class ValidateNodeInfo(val url: String, val error: NodeInfo.Error) : Error( data class ValidateNodeInfo(val url: String, val error: NodeInfo.Error) : Error(
R.string.server_repository_error_validate_node_info, R.string.server_repository_error_validate_node_info,
url, arrayOf(url),
source = error, cause = error,
) )
data class GetInstanceInfoV1(val throwable: Throwable) : Error( data class GetInstanceInfoV1(val throwable: Throwable) : Error(
R.string.server_repository_error_get_instance_info, R.string.server_repository_error_get_instance_info,
throwable.localizedMessage, throwable.localizedMessage?.let { arrayOf(it) }.orEmpty(),
) )
data class Capabilities(val error: Server.Error) : Error( data class Capabilities(val error: Server.Error) : Error(
R.string.server_repository_error_capabilities, R.string.server_repository_error_capabilities,
source = error, cause = error,
) )
} }
} }

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0)" variant="all" version="8.3.0"> <issues format="6" by="lint 8.3.2" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.2)" variant="all" version="8.3.2">
</issues> </issues>

View File

@ -17,7 +17,6 @@
package app.pachli.core.network package app.pachli.core.network
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import app.pachli.core.common.PachliError import app.pachli.core.common.PachliError
@ -296,16 +295,13 @@ data class Server(
} }
/** Errors that can occur when processing server capabilities */ /** Errors that can occur when processing server capabilities */
sealed class Error( sealed interface Error : PachliError {
@StringRes resourceId: Int,
vararg formatArgs: String,
) : PachliError(resourceId, *formatArgs) {
/** Could not parse the server's version string */ /** Could not parse the server's version string */
data class UnparseableVersion(val version: String, val throwable: Throwable) : Error( data class UnparseableVersion(val version: String, val throwable: Throwable) : Error {
R.string.server_error_unparseable_version, override val resourceId = R.string.server_error_unparseable_version
version, override val formatArgs: Array<String> = arrayOf(version, throwable.localizedMessage ?: "")
throwable.localizedMessage, override val cause: PachliError? = null
) }
} }
} }

View File

@ -77,10 +77,11 @@ data class NodeInfo(val software: Software) {
} }
sealed class Error( sealed class Error(
@StringRes resourceId: Int, @StringRes override val resourceId: Int,
vararg formatArgs: String, ) : PachliError {
source: PachliError? = null, override val formatArgs = emptyArray<String>()
) : PachliError(resourceId, *formatArgs, source = source) { override val cause: PachliError? = null
data object NoSoftwareBlock : Error(R.string.node_info_error_no_software) data object NoSoftwareBlock : Error(R.string.node_info_error_no_software)
data object NoSoftwareName : Error(R.string.node_info_error_no_software_name) data object NoSoftwareName : Error(R.string.node_info_error_no_software_name)
data object NoSoftwareVersion : Error(R.string.node_info_error_no_software_version) data object NoSoftwareVersion : Error(R.string.node_info_error_no_software_version)

View File

@ -17,6 +17,10 @@
package app.pachli.core.network.retrofit.apiresult package app.pachli.core.network.retrofit.apiresult
import androidx.annotation.StringRes
import app.pachli.core.common.PachliError
import app.pachli.core.network.R
import app.pachli.core.network.extensions.getServerErrorMessage
import com.github.michaelbull.result.Err import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
@ -45,50 +49,57 @@ data class ApiResponse<out T>(
/** /**
* A failed response from an API call. * A failed response from an API call.
*
* @param resourceId String resource ID of the error message.
* @param throwable The [Throwable] that caused this error. The server
* message (if it exists), or [Throwable.getLocalizedMessage] will be
* interpolated in to this string at `%1$s`.
*/ */
interface ApiError { sealed class ApiError(
// This has to be Throwable, not Exception, because Retrofit exposes @StringRes override val resourceId: Int,
// errors as Throwable val throwable: Throwable,
val throwable: Throwable ) : PachliError {
override val formatArgs = (
throwable.getServerErrorMessage() ?: throwable.localizedMessage
)?.let { arrayOf(it) }.orEmpty()
override val cause: PachliError? = null
companion object { companion object {
fun from(exception: Throwable): ApiError { fun from(throwable: Throwable): ApiError {
return when (exception) { return when (throwable) {
is HttpException -> when (exception.code()) { is HttpException -> when (throwable.code()) {
in 400..499 -> ClientError.from(exception) in 400..499 -> ClientError.from(throwable)
in 500..599 -> ServerError.from(exception) in 500..599 -> ServerError.from(throwable)
else -> Unknown(exception) else -> Unknown(throwable)
} }
is JsonDataException -> JsonParse(exception) is JsonDataException -> JsonParseError(throwable)
is IOException -> IO(exception) is IOException -> IoError(throwable)
else -> Unknown(exception) else -> Unknown(throwable)
} }
} }
} }
data class Unknown(override val throwable: Throwable) : ApiError data class Unknown(val exception: Throwable) : ApiError(
R.string.error_generic_fmt,
exception,
)
} }
sealed interface HttpError : ApiError { sealed class HttpError(
override val throwable: HttpException @StringRes override val resourceId: Int,
open val exception: HttpException,
/** ) : ApiError(resourceId, exception)
* The error message for this error, one of (in preference order):
*
* - The error body of the response that created this error
* - The throwable.message
* - Literal string "Unknown"
*/
val message
get() = throwable.response()?.errorBody()?.string() ?: throwable.message() ?: "Unknown"
}
/** 4xx errors */ /** 4xx errors */
sealed interface ClientError : HttpError { sealed class ClientError(
@StringRes override val resourceId: Int,
exception: HttpException,
) : HttpError(resourceId, exception) {
companion object { companion object {
fun from(exception: HttpException): ClientError { fun from(exception: HttpException): ClientError {
return when (exception.code()) { return when (exception.code()) {
400 -> BadRequest(exception)
401 -> Unauthorized(exception) 401 -> Unauthorized(exception)
404 -> NotFound(exception) 404 -> NotFound(exception)
410 -> Gone(exception) 410 -> Gone(exception)
@ -97,14 +108,32 @@ sealed interface ClientError : HttpError {
} }
} }
data class Unauthorized(override val throwable: HttpException) : ClientError /** 400 Bad request */
data class NotFound(override val throwable: HttpException) : ClientError data class BadRequest(override val exception: HttpException) :
data class Gone(override val throwable: HttpException) : ClientError ClientError(R.string.error_generic_fmt, exception)
data class UnknownClientError(override val throwable: HttpException) : ClientError
/** 401 Unauthorized */
data class Unauthorized(override val exception: HttpException) :
ClientError(R.string.error_generic_fmt, exception)
/** 404 Not found */
data class NotFound(override val exception: HttpException) :
ClientError(R.string.error_404_not_found_fmt, exception)
/** 410 Gone */
data class Gone(override val exception: HttpException) :
ClientError(R.string.error_generic_fmt, exception)
/** All other 4xx client errors */
data class UnknownClientError(override val exception: HttpException) :
ClientError(R.string.error_generic_fmt, exception)
} }
/** 5xx errors */ /** 5xx errors */
sealed interface ServerError : HttpError { sealed class ServerError(
@StringRes override val resourceId: Int,
exception: HttpException,
) : HttpError(resourceId, exception) {
companion object { companion object {
fun from(exception: HttpException): ServerError { fun from(exception: HttpException): ServerError {
return when (exception.code()) { return when (exception.code()) {
@ -117,15 +146,32 @@ sealed interface ServerError : HttpError {
} }
} }
data class Internal(override val throwable: HttpException) : ServerError /** 500 Internal error */
data class NotImplemented(override val throwable: HttpException) : ServerError data class Internal(override val exception: HttpException) :
data class BadGateway(override val throwable: HttpException) : ServerError ServerError(R.string.error_generic_fmt, exception)
data class ServiceUnavailable(override val throwable: HttpException) : ServerError
data class UnknownServerError(override val throwable: HttpException) : ServerError /** 501 Not implemented */
data class NotImplemented(override val exception: HttpException) :
ServerError(R.string.error_404_not_found_fmt, exception)
/** 502 Bad gateway */
data class BadGateway(override val exception: HttpException) :
ServerError(R.string.error_generic_fmt, exception)
/** 503 Service unavailable */
data class ServiceUnavailable(override val exception: HttpException) :
ServerError(R.string.error_generic_fmt, exception)
/** All other 5xx server errors */
data class UnknownServerError(override val exception: HttpException) :
ServerError(R.string.error_generic_fmt, exception)
} }
data class JsonParse(override val throwable: JsonDataException) : ApiError
sealed interface NetworkError : ApiError data class JsonParseError(val exception: JsonDataException) :
data class IO(override val throwable: Exception) : NetworkError ApiError(R.string.error_json_data_fmt, exception)
data class IoError(val exception: IOException) :
ApiError(R.string.error_network_fmt, exception)
/** /**
* Creates an [ApiResult] from a [Response]. * Creates an [ApiResult] from a [Response].

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">وقع خطأ: %s</string>
<string name="error_404_not_found_fmt">خادمك لا يدعم هذه الميزة: %1$s</string>
<string name="error_network_fmt">وقع خطأ في الشبكة: %s</string>
</resources>

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">Es ist ein Fehler aufgetreten: %s</string>
<string name="error_404_not_found_fmt">Dein Server unterstützt diese Funktion nicht: %1$s</string>
<string name="error_network_fmt">Ein Netzwerkfehler ist aufgetreten: %s</string>
</resources>

View File

@ -1,2 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">An error occurred: %s</string>
<string name="error_404_not_found_fmt">Your server does not support this feature: %1$s</string>
</resources>

View File

@ -4,4 +4,8 @@
<string name="node_info_error_no_software_version">versión del programa faltante, vacía o en blanco</string> <string name="node_info_error_no_software_version">versión del programa faltante, vacía o en blanco</string>
<string name="server_error_unparseable_version">no se pudo analizar \"%1$s\" como una versión: %2$s</string> <string name="server_error_unparseable_version">no se pudo analizar \"%1$s\" como una versión: %2$s</string>
<string name="node_info_error_no_software">no hay bloque sobre el programa</string> <string name="node_info_error_no_software">no hay bloque sobre el programa</string>
</resources> <string name="error_generic_fmt">Ha ocurrido un error: %s</string>
<string name="error_404_not_found_fmt">Su servidor no soporta esta función: %1$s</string>
<string name="error_json_data_fmt">Tu servidor devolvió una respuesta inválida: %1$s</string>
<string name="error_network_fmt">Ha ocurrido un error de red: %s</string>
</resources>

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">Tapahtui virhe: %s</string>
<string name="error_404_not_found_fmt">Palvelimesi ei tue tätä ominaisuutta: %1$s</string>
<string name="error_network_fmt">Tapahtui verkkovirhe: %s</string>
</resources>

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">Une erreur s\'est produite: %s</string>
<string name="error_404_not_found_fmt">Votre serveur ne prend pas en charge cette fonctionnalité: %1$s</string>
<string name="error_network_fmt">Une erreur réseau s\'est produite: %s</string>
</resources>

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">Terjadi error: %s</string>
<string name="error_404_not_found_fmt">Server Anda tidak mendukung fitur ini: %1$s</string>
<string name="error_network_fmt">Jaringan error: %s</string>
</resources>

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">Si è verificato un errore: %s</string>
<string name="error_404_not_found_fmt">Il tuo server non supporta questa feature: %1$s</string>
<string name="error_network_fmt">Si è verificato un errore di rete: %s</string>
</resources>

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">エラーが発生しました: %s</string>
<string name="error_404_not_found_fmt">あなたのサーバーはこの機能をサポートしていません: %1$s</string>
<string name="error_network_fmt">ネットワーク エラーが発生しました: %s</string>
</resources>

View File

@ -1,2 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">Tella-d tuccḍa: %s</string>
</resources>

View File

@ -4,4 +4,7 @@
<string name="node_info_error_no_software_name">software naam mist, is leeg of blanco</string> <string name="node_info_error_no_software_name">software naam mist, is leeg of blanco</string>
<string name="node_info_error_no_software_version">software versie mist, is leeg of blanco</string> <string name="node_info_error_no_software_version">software versie mist, is leeg of blanco</string>
<string name="server_error_unparseable_version">kon \"%1$s\" niet verwerken als een versie: %2$s</string> <string name="server_error_unparseable_version">kon \"%1$s\" niet verwerken als een versie: %2$s</string>
</resources> <string name="error_generic_fmt">Er deed zich een fout voor: %s</string>
<string name="error_404_not_found_fmt">Je server beschikt niet over ondersteuning voor deze feature: %1$s</string>
<string name="error_network_fmt">Er deed zich een netwerkfout voor: %s</string>
</resources>

View File

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="error_generic_fmt">Ocorreu um erro: %s</string>
<string name="error_404_not_found_fmt">Tua instância não suporta este recurso: %1$s</string>
<string name="error_network_fmt">Ocorreu um erro de rede: %s</string>
</resources>

View File

@ -4,4 +4,7 @@
<string name="node_info_error_no_software">innehöll inget mjukvarublock</string> <string name="node_info_error_no_software">innehöll inget mjukvarublock</string>
<string name="node_info_error_no_software_name">mjukvarunamnet saknas, är tomt eller blankt</string> <string name="node_info_error_no_software_name">mjukvarunamnet saknas, är tomt eller blankt</string>
<string name="server_error_unparseable_version">Kunde inte analysera \"%1$s\" som en version: %2$s</string> <string name="server_error_unparseable_version">Kunde inte analysera \"%1$s\" som en version: %2$s</string>
</resources> <string name="error_generic_fmt">Ett fel har uppstått: %s</string>
<string name="error_404_not_found_fmt">Din server stöder inte denna funktion: %1$s</string>
<string name="error_network_fmt">Ett nätverksfel har uppstått: %s</string>
</resources>

View File

@ -21,4 +21,8 @@
<string name="node_info_error_no_software_version">software version is missing, empty, or blank</string> <string name="node_info_error_no_software_version">software version is missing, empty, or blank</string>
<string name="server_error_unparseable_version">could not parse \"%1$s\" as a version: %2$s</string> <string name="server_error_unparseable_version">could not parse \"%1$s\" as a version: %2$s</string>
<string name="error_generic_fmt">An error occurred: %s</string>
<string name="error_404_not_found_fmt">Your server does not support this feature: %1$s</string>
<string name="error_json_data_fmt">Your server returned an invalid response: %1$s</string>
<string name="error_network_fmt">A network error occurred: %s</string>
</resources> </resources>

View File

@ -89,11 +89,10 @@ class ApiResultCallTest {
override fun onResponse(call: Call<ApiResult<String>>, response: Response<ApiResult<String>>) { override fun onResponse(call: Call<ApiResult<String>>, response: Response<ApiResult<String>>) {
val error = response.body()?.getError() as? ClientError.NotFound val error = response.body()?.getError() as? ClientError.NotFound
assertThat(error).isInstanceOf(ClientError.NotFound::class.java) assertThat(error).isInstanceOf(ClientError.NotFound::class.java)
assertThat(error?.message).isEqualTo("not found")
val throwable = error?.throwable val exception = error?.exception
assertThat(throwable).isInstanceOf(HttpException::class.java) assertThat(exception).isInstanceOf(HttpException::class.java)
assertThat(throwable?.code()).isEqualTo(404) assertThat(exception?.code()).isEqualTo(404)
} }
override fun onFailure(call: Call<ApiResult<String>>, t: Throwable) { override fun onFailure(call: Call<ApiResult<String>>, t: Throwable) {
@ -107,7 +106,7 @@ class ApiResultCallTest {
@Test @Test
fun `should parse call with IOException as ApiResult-failure`() { fun `should parse call with IOException as ApiResult-failure`() {
val error = Err(IO(IOException())) val error = Err(IoError(IOException()))
networkApiResultCall.enqueue( networkApiResultCall.enqueue(
object : Callback<ApiResult<String>> { object : Callback<ApiResult<String>> {

View File

@ -140,8 +140,8 @@ class ApiTest {
val error = responseObject as? ServerError.Internal val error = responseObject as? ServerError.Internal
assertThat(error).isInstanceOf(ServerError.Internal::class.java) assertThat(error).isInstanceOf(ServerError.Internal::class.java)
assertThat(error?.throwable?.code()).isEqualTo(500) assertThat(error?.exception?.code()).isEqualTo(500)
assertThat(error?.throwable?.message()).isEqualTo("Server Error") assertThat(error?.exception?.message()).isEqualTo("Server Error")
} }
@Test @Test
@ -155,8 +155,8 @@ class ApiTest {
val error = responseObject as? ServerError.Internal val error = responseObject as? ServerError.Internal
assertThat(error).isInstanceOf(ServerError.Internal::class.java) assertThat(error).isInstanceOf(ServerError.Internal::class.java)
assertThat(error?.throwable?.code()).isEqualTo(500) assertThat(error?.exception?.code()).isEqualTo(500)
assertThat(error?.throwable?.message()).isEqualTo("Server Error") assertThat(error?.exception?.message()).isEqualTo("Server Error")
} }
@Test @Test
@ -167,9 +167,9 @@ class ApiTest {
api.getSiteAsync() api.getSiteAsync()
} }
val error = responseObject.getError() as? IO val error = responseObject.getError() as? IoError
assertThat(error).isInstanceOf(IO::class.java) assertThat(error).isInstanceOf(IoError::class.java)
} }
@Test @Test
@ -177,9 +177,9 @@ class ApiTest {
mockWebServer.enqueue(MockResponse().apply { socketPolicy = SocketPolicy.DISCONNECT_AFTER_REQUEST }) mockWebServer.enqueue(MockResponse().apply { socketPolicy = SocketPolicy.DISCONNECT_AFTER_REQUEST })
val responseObject = runBlocking { api.getSiteSync() } val responseObject = runBlocking { api.getSiteSync() }
val error = responseObject.getError() as? IO val error = responseObject.getError() as? IoError
assertThat(error).isInstanceOf(IO::class.java) assertThat(error).isInstanceOf(IoError::class.java)
} }
@Test @Test
@ -189,9 +189,9 @@ class ApiTest {
mockWebServer.enqueue(response) mockWebServer.enqueue(response)
val responseObject = api.getSitesAsync().getError() val responseObject = api.getSitesAsync().getError()
val error = responseObject as? JsonParse val error = responseObject as? JsonParseError
assertThat(error).isInstanceOf(JsonParse::class.java) assertThat(error).isInstanceOf(JsonParseError::class.java)
} }
@Test @Test
@ -201,11 +201,11 @@ class ApiTest {
val responseObject = api.getSitesAsync().getError() val responseObject = api.getSitesAsync().getError()
val error = responseObject as? IO val error = responseObject as? IoError
// Moshi reports invalid JSON as an IoException wrapping a JsonEncodingException // Moshi reports invalid JSON as an IoException wrapping a JsonEncodingException
assertThat(error).isInstanceOf(IO::class.java) assertThat(error).isInstanceOf(IoError::class.java)
assertThat(error?.throwable).isInstanceOf(JsonEncodingException::class.java) assertThat(error?.exception).isInstanceOf(JsonEncodingException::class.java)
} }
@Test @Test

View File

@ -32,6 +32,7 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import app.pachli.core.common.PachliError
import app.pachli.core.common.extensions.visible import app.pachli.core.common.extensions.visible
import app.pachli.core.ui.databinding.ViewBackgroundMessageBinding import app.pachli.core.ui.databinding.ViewBackgroundMessageBinding
import app.pachli.core.ui.extensions.getDrawableRes import app.pachli.core.ui.extensions.getDrawableRes
@ -99,6 +100,10 @@ class BackgroundMessageView @JvmOverloads constructor(
setup(throwable.getDrawableRes(), throwable.getErrorString(context), listener) setup(throwable.getDrawableRes(), throwable.getErrorString(context), listener)
} }
fun setup(error: PachliError, listener: ((v: View) -> Unit)? = null) {
setup(error.getDrawableRes(), error.fmt(context), listener)
}
fun setup(message: BackgroundMessage, listener: ((v: View) -> Unit)? = null) { fun setup(message: BackgroundMessage, listener: ((v: View) -> Unit)? = null) {
setup(message.drawableRes, message.stringRes, listener) setup(message.drawableRes, message.stringRes, listener)
} }

View File

@ -0,0 +1,36 @@
/*
* Copyright 2024 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.core.ui.extensions
import androidx.annotation.DrawableRes
import app.pachli.core.common.PachliError
import app.pachli.core.network.retrofit.apiresult.ClientError
import app.pachli.core.network.retrofit.apiresult.HttpError
import app.pachli.core.network.retrofit.apiresult.IoError
import app.pachli.core.ui.R
/** @return A drawable resource to accompany the error message for this [PachliError]. */
@DrawableRes
fun PachliError.getDrawableRes(): Int = when (this) {
is IoError -> R.drawable.errorphant_offline
is HttpError -> when (this) {
is ClientError.NotFound -> R.drawable.elephant_friend_empty
else -> R.drawable.errorphant_offline
}
else -> R.drawable.errorphant_offline
}

View File

@ -18,7 +18,9 @@
package app.pachli.core.ui.extensions package app.pachli.core.ui.extensions
import android.content.Context import android.content.Context
import androidx.annotation.DrawableRes
import app.pachli.core.common.string.unicodeWrap import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.network.R as NR
import app.pachli.core.network.extensions.getServerErrorMessage import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.ui.R import app.pachli.core.ui.R
import com.squareup.moshi.JsonDataException import com.squareup.moshi.JsonDataException
@ -26,6 +28,7 @@ import java.io.IOException
import retrofit2.HttpException import retrofit2.HttpException
/** @return A drawable resource to accompany the error message for this throwable */ /** @return A drawable resource to accompany the error message for this throwable */
@DrawableRes
fun Throwable.getDrawableRes(): Int = when (this) { fun Throwable.getDrawableRes(): Int = when (this) {
is IOException -> R.drawable.errorphant_offline is IOException -> R.drawable.errorphant_offline
is HttpException -> { is HttpException -> {
@ -41,15 +44,15 @@ fun Throwable.getDrawableRes(): Int = when (this) {
/** @return A unicode-wrapped string error message for this throwable */ /** @return A unicode-wrapped string error message for this throwable */
fun Throwable.getErrorString(context: Context): String = ( fun Throwable.getErrorString(context: Context): String = (
getServerErrorMessage() ?: when (this) { getServerErrorMessage() ?: when (this) {
is IOException -> String.format(context.getString(R.string.error_network_fmt), localizedMessage) is IOException -> String.format(context.getString(NR.string.error_network_fmt), localizedMessage)
is HttpException -> { is HttpException -> {
if (code() == 404) { if (code() == 404) {
String.format(context.getString(R.string.error_404_not_found_fmt), localizedMessage) String.format(context.getString(NR.string.error_404_not_found_fmt), localizedMessage)
} else { } else {
String.format(context.getString(R.string.error_generic_fmt), localizedMessage) String.format(context.getString(NR.string.error_generic_fmt), localizedMessage)
} }
} }
is JsonDataException -> String.format(context.getString(R.string.error_json_data_fmt), localizedMessage) is JsonDataException -> String.format(context.getString(NR.string.error_json_data_fmt), localizedMessage)
else -> String.format(context.getString(R.string.error_generic_fmt), localizedMessage) else -> String.format(context.getString(NR.string.error_generic_fmt), localizedMessage)
} }
).unicodeWrap() ).unicodeWrap()

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">حدث خطأ في الشبكة! يرجى التحقق من اتصالك ثم أعد المحاولة!</string> <string name="error_network">حدث خطأ في الشبكة! يرجى التحقق من اتصالك ثم أعد المحاولة!</string>
<string name="error_network_fmt">وقع خطأ في الشبكة: %s</string>
<string name="error_generic_fmt">وقع خطأ: %s</string>
<string name="error_404_not_found_fmt">خادمك لا يدعم هذه الميزة: %1$s</string>
<string name="error_generic">وقع هناك خطأ.</string> <string name="error_generic">وقع هناك خطأ.</string>
<string name="message_empty">لا شيء هنا.</string> <string name="message_empty">لا شيء هنا.</string>
<string name="action_retry">أعد المحاولة</string> <string name="action_retry">أعد المحاولة</string>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Ein Netzwerkfehler ist aufgetreten. Bitte überprüfe deine Internetverbindung und versuche es erneut.</string> <string name="error_network">Ein Netzwerkfehler ist aufgetreten. Bitte überprüfe deine Internetverbindung und versuche es erneut.</string>
<string name="error_network_fmt">Ein Netzwerkfehler ist aufgetreten: %s</string>
<string name="error_generic_fmt">Es ist ein Fehler aufgetreten: %s</string>
<string name="error_404_not_found_fmt">Dein Server unterstützt diese Funktion nicht: %1$s</string>
<string name="error_generic">Ein Fehler ist aufgetreten.</string> <string name="error_generic">Ein Fehler ist aufgetreten.</string>
<string name="message_empty">Hier ist nichts.</string> <string name="message_empty">Hier ist nichts.</string>
<string name="action_retry">Erneut versuchen</string> <string name="action_retry">Erneut versuchen</string>

View File

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">A network error occurred! Please check your connection and try again!</string> <string name="error_network">A network error occurred! Please check your connection and try again!</string>
<string name="error_generic_fmt">An error occurred: %s</string>
<string name="error_404_not_found_fmt">Your server does not support this feature: %1$s</string>
<string name="error_generic">An error occurred.</string> <string name="error_generic">An error occurred.</string>
</resources> </resources>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="error_network">Ha ocurrido un error de red. Por favor, comprueba tu conexión e inténtalo de nuevo.</string> <string name="error_network">Ha ocurrido un error de red. Por favor, comprueba tu conexión e inténtalo de nuevo.</string>
<string name="error_network_fmt">Ha ocurrido un error de red: %s</string>
<string name="error_generic_fmt">Ha ocurrido un error: %s</string>
<string name="error_404_not_found_fmt">Su servidor no soporta esta función: %1$s</string>
<string name="error_generic">Ha ocurrido un error.</string> <string name="error_generic">Ha ocurrido un error.</string>
<string name="message_empty">Nada aquí.</string> <string name="message_empty">Nada aquí.</string>
<string name="action_retry">Reintentar</string> <string name="action_retry">Reintentar</string>
@ -11,6 +8,5 @@
<string name="action_view_profile">Perfil</string> <string name="action_view_profile">Perfil</string>
<string name="action_more">Más</string> <string name="action_more">Más</string>
<string name="action_refresh">Recargar</string> <string name="action_refresh">Recargar</string>
<string name="error_json_data_fmt">Tu servidor devolvió una respuesta inválida: %1$s</string>
<string name="url_domain_notifier">\u0020(🔗 %s)</string> <string name="url_domain_notifier">\u0020(🔗 %s)</string>
</resources> </resources>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Verkkovirhe. Tarkista yhteytesi ja yritä uudelleen.</string> <string name="error_network">Verkkovirhe. Tarkista yhteytesi ja yritä uudelleen.</string>
<string name="error_network_fmt">Tapahtui verkkovirhe: %s</string>
<string name="error_generic_fmt">Tapahtui virhe: %s</string>
<string name="error_404_not_found_fmt">Palvelimesi ei tue tätä ominaisuutta: %1$s</string>
<string name="error_generic">Tapahtui virhe.</string> <string name="error_generic">Tapahtui virhe.</string>
<string name="message_empty">Täällä ei ole mitään.</string> <string name="message_empty">Täällä ei ole mitään.</string>
<string name="action_retry">Yritä uudelleen</string> <string name="action_retry">Yritä uudelleen</string>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Une erreur réseau sest produite ! Veuillez vérifier votre connexion puis réessayez !</string> <string name="error_network">Une erreur réseau sest produite ! Veuillez vérifier votre connexion puis réessayez !</string>
<string name="error_network_fmt">Une erreur réseau s\'est produite: %s</string>
<string name="error_generic_fmt">Une erreur s\'est produite: %s</string>
<string name="error_404_not_found_fmt">Votre serveur ne prend pas en charge cette fonctionnalité: %1$s</string>
<string name="error_generic">Une erreur sest produite.</string> <string name="error_generic">Une erreur sest produite.</string>
<string name="message_empty">Rien ici.</string> <string name="message_empty">Rien ici.</string>
<string name="action_retry">Réessayer</string> <string name="action_retry">Réessayer</string>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Terjadi kesalahan pada jaringan! Harap periksa koneksi Anda dan coba lagi!</string> <string name="error_network">Terjadi kesalahan pada jaringan! Harap periksa koneksi Anda dan coba lagi!</string>
<string name="error_network_fmt">Jaringan error: %s</string>
<string name="error_generic_fmt">Terjadi error: %s</string>
<string name="error_404_not_found_fmt">Server Anda tidak mendukung fitur ini: %1$s</string>
<string name="error_generic">Terjadi kesalahan.</string> <string name="error_generic">Terjadi kesalahan.</string>
<string name="message_empty">Tidak ada apa pun disini.</string> <string name="message_empty">Tidak ada apa pun disini.</string>
<string name="action_retry">Coba lagi</string> <string name="action_retry">Coba lagi</string>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Si è verificato un errore di rete. Per favore controlla la tua connessione e riprova.</string> <string name="error_network">Si è verificato un errore di rete. Per favore controlla la tua connessione e riprova.</string>
<string name="error_network_fmt">Si è verificato un errore di rete: %s</string>
<string name="error_generic_fmt">Si è verificato un errore: %s</string>
<string name="error_404_not_found_fmt">Il tuo server non supporta questa feature: %1$s</string>
<string name="error_generic">Si è verificato un errore.</string> <string name="error_generic">Si è verificato un errore.</string>
<string name="message_empty">Qui non c\'è nulla.</string> <string name="message_empty">Qui non c\'è nulla.</string>
<string name="action_retry">Riprova</string> <string name="action_retry">Riprova</string>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">ネットワークエラーが発生しました。接続を確認してもう一度試してください。</string> <string name="error_network">ネットワークエラーが発生しました。接続を確認してもう一度試してください。</string>
<string name="error_network_fmt">ネットワーク エラーが発生しました: %s</string>
<string name="error_generic_fmt">エラーが発生しました: %s</string>
<string name="error_404_not_found_fmt">あなたのサーバーはこの機能をサポートしていません: %1$s</string>
<string name="error_generic">エラーが発生しました。</string> <string name="error_generic">エラーが発生しました。</string>
<string name="message_empty">何もありません。</string> <string name="message_empty">何もありません。</string>
<string name="action_retry">再試行</string> <string name="action_retry">再試行</string>

View File

@ -5,7 +5,6 @@
<string name="action_retry">Ɛreḍ tikkelt-nniḍen</string> <string name="action_retry">Ɛreḍ tikkelt-nniḍen</string>
<string name="action_view_profile">Amaɣnu</string> <string name="action_view_profile">Amaɣnu</string>
<string name="action_more">Ugar</string> <string name="action_more">Ugar</string>
<string name="error_generic_fmt">Tella-d tuccḍa: %s</string>
<string name="action_refresh">Smiren</string> <string name="action_refresh">Smiren</string>
<string name="button_done">Immed</string> <string name="button_done">Immed</string>
</resources> </resources>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Er deed zich een netwerkfout voor. Controleer je verbinding en probeer opnieuw.</string> <string name="error_network">Er deed zich een netwerkfout voor. Controleer je verbinding en probeer opnieuw.</string>
<string name="error_network_fmt">Er deed zich een netwerkfout voor: %s</string>
<string name="error_generic_fmt">Er deed zich een fout voor: %s</string>
<string name="error_404_not_found_fmt">Je server beschikt niet over ondersteuning voor deze feature: %1$s</string>
<string name="error_generic">Er deed zich een fout voor.</string> <string name="error_generic">Er deed zich een fout voor.</string>
<string name="message_empty">Hier is niets.</string> <string name="message_empty">Hier is niets.</string>
<string name="action_retry">Opnieuw proberen</string> <string name="action_retry">Opnieuw proberen</string>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Ocorreu um erro de rede. Por favor, verifique tua Internet e tente novamente.</string> <string name="error_network">Ocorreu um erro de rede. Por favor, verifique tua Internet e tente novamente.</string>
<string name="error_network_fmt">Ocorreu um erro de rede: %s</string>
<string name="error_generic_fmt">Ocorreu um erro: %s</string>
<string name="error_404_not_found_fmt">Tua instância não suporta este recurso: %1$s</string>
<string name="error_generic">Um erro ocorreu.</string> <string name="error_generic">Um erro ocorreu.</string>
<string name="message_empty">Nada aqui.</string> <string name="message_empty">Nada aqui.</string>
<string name="action_retry">Tentar novamente</string> <string name="action_retry">Tentar novamente</string>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">Ett nätverksfel uppstod. Kontrollera din anslutning och försök igen.</string> <string name="error_network">Ett nätverksfel uppstod. Kontrollera din anslutning och försök igen.</string>
<string name="error_network_fmt">Ett nätverksfel har uppstått: %s</string>
<string name="error_generic_fmt">Ett fel har uppstått: %s</string>
<string name="error_404_not_found_fmt">Din server stöder inte denna funktion: %1$s</string>
<string name="error_generic">Ett fel har uppstått.</string> <string name="error_generic">Ett fel har uppstått.</string>
<string name="message_empty">Ingenting här.</string> <string name="message_empty">Ingenting här.</string>
<string name="action_retry">Försök igen</string> <string name="action_retry">Försök igen</string>

View File

@ -1,10 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="error_network">A network error occurred. Please check your connection and try again.</string> <string name="error_network">A network error occurred. Please check your connection and try again.</string>
<string name="error_network_fmt">A network error occurred: %s</string>
<string name="error_generic_fmt">An error occurred: %s</string>
<string name="error_404_not_found_fmt">Your server does not support this feature: %1$s</string>
<string name="error_json_data_fmt">Your server returned an invalid response: %1$s</string>
<string name="error_generic">An error occurred.</string> <string name="error_generic">An error occurred.</string>
<string name="message_empty">Nothing here.</string> <string name="message_empty">Nothing here.</string>
<string name="action_retry">Retry</string> <string name="action_retry">Retry</string>

View File

@ -34,6 +34,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import app.pachli.core.activity.emojify import app.pachli.core.activity.emojify
import app.pachli.core.activity.loadAvatar import app.pachli.core.activity.loadAvatar
import app.pachli.core.common.PachliError
import app.pachli.core.common.extensions.hide import app.pachli.core.common.extensions.hide
import app.pachli.core.common.extensions.show import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.viewBinding import app.pachli.core.common.extensions.viewBinding
@ -141,7 +142,7 @@ class AccountsInListFragment : DialogFragment() {
launch { launch {
viewModel.errors.collect { viewModel.errors.collect {
handleError(it.throwable) handleError(it)
} }
} }
} }
@ -172,7 +173,7 @@ class AccountsInListFragment : DialogFragment() {
if (it is Accounts.Loaded) adapter.submitList(it.accounts) if (it is Accounts.Loaded) adapter.submitList(it.accounts)
}.onFailure { }.onFailure {
binding.messageView.show() binding.messageView.show()
handleError(it.throwable) handleError(it)
} }
} }
@ -196,11 +197,11 @@ class AccountsInListFragment : DialogFragment() {
} }
}.onFailure { }.onFailure {
Timber.w(it.throwable, "Error searching for accounts in list") Timber.w(it.throwable, "Error searching for accounts in list")
handleError(it.throwable) handleError(it)
} }
} }
private fun handleError(error: Throwable) { private fun handleError(error: PachliError) {
binding.messageView.show() binding.messageView.show()
binding.messageView.setup(error) { binding.messageView.setup(error) {
binding.messageView.hide() binding.messageView.hide()

View File

@ -43,14 +43,12 @@ import app.pachli.core.activity.extensions.startActivityWithDefaultTransition
import app.pachli.core.common.extensions.hide import app.pachli.core.common.extensions.hide
import app.pachli.core.common.extensions.show import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.viewBinding import app.pachli.core.common.extensions.viewBinding
import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.data.repository.Lists import app.pachli.core.data.repository.Lists
import app.pachli.core.data.repository.ListsError
import app.pachli.core.data.repository.ListsRepository.Companion.compareByListTitle import app.pachli.core.data.repository.ListsRepository.Companion.compareByListTitle
import app.pachli.core.navigation.TimelineActivityIntent import app.pachli.core.navigation.TimelineActivityIntent
import app.pachli.core.network.model.MastoList import app.pachli.core.network.model.MastoList
import app.pachli.core.network.model.UserListRepliesPolicy import app.pachli.core.network.model.UserListRepliesPolicy
import app.pachli.core.network.retrofit.apiresult.ApiError
import app.pachli.core.network.retrofit.apiresult.NetworkError
import app.pachli.core.ui.BackgroundMessage import app.pachli.core.ui.BackgroundMessage
import app.pachli.core.ui.extensions.await import app.pachli.core.ui.extensions.await
import app.pachli.feature.lists.databinding.ActivityListsBinding import app.pachli.feature.lists.databinding.ActivityListsBinding
@ -113,29 +111,7 @@ class ListsActivity : BaseActivity(), MenuProvider {
lifecycleScope.launch { lifecycleScope.launch {
viewModel.errors.collect { error -> viewModel.errors.collect { error ->
when (error) { showMessage(error.fmt(this@ListsActivity))
is Error.Create -> showMessage(
String.format(
getString(R.string.error_create_list_fmt),
error.title.unicodeWrap(),
error.throwable.message.unicodeWrap(),
),
)
is Error.Delete -> showMessage(
String.format(
getString(R.string.error_delete_list_fmt),
error.title.unicodeWrap(),
error.throwable.message.unicodeWrap(),
),
)
is Error.Update -> showMessage(
String.format(
getString(R.string.error_rename_list_fmt),
error.title.unicodeWrap(),
error.throwable.message.unicodeWrap(),
),
)
}
} }
} }
@ -220,17 +196,13 @@ class ListsActivity : BaseActivity(), MenuProvider {
if (result == AlertDialog.BUTTON_POSITIVE) viewModel.deleteList(list.id, list.title) if (result == AlertDialog.BUTTON_POSITIVE) viewModel.deleteList(list.id, list.title)
} }
private fun bind(state: Result<Lists, ApiError>) { private fun bind(state: Result<Lists, ListsError>) {
state.onFailure { state.onFailure {
binding.listsRecycler.hide() binding.listsRecycler.hide()
binding.messageView.show() binding.messageView.show()
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
if (it is NetworkError) { binding.messageView.setup(it) { viewModel.refresh() }
binding.messageView.setup(BackgroundMessage.Network()) { viewModel.refresh() }
} else {
binding.messageView.setup(BackgroundMessage.GenericError()) { viewModel.refresh() }
}
} }
state.onSuccess { lists -> state.onSuccess { lists ->

View File

@ -155,7 +155,7 @@ class ListsForAccountFragment : DialogFragment() {
binding.listsView.hide() binding.listsView.hide()
binding.messageView.apply { binding.messageView.apply {
show() show()
setup(it.throwable) { setup(it) {
viewModel.refresh() viewModel.refresh()
load() load()
} }

View File

@ -24,7 +24,6 @@ import app.pachli.core.data.repository.Lists
import app.pachli.core.data.repository.ListsError import app.pachli.core.data.repository.ListsError
import app.pachli.core.data.repository.ListsRepository import app.pachli.core.data.repository.ListsRepository
import app.pachli.core.network.model.MastoList import app.pachli.core.network.model.MastoList
import app.pachli.core.network.retrofit.apiresult.ApiError
import com.github.michaelbull.result.Err import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
@ -67,7 +66,7 @@ class ListsForAccountViewModel @AssistedInject constructor(
private val _listsWithMembership = MutableStateFlow<Result<ListsWithMembership, FlowError>>(Ok(ListsWithMembership.Loading)) private val _listsWithMembership = MutableStateFlow<Result<ListsWithMembership, FlowError>>(Ok(ListsWithMembership.Loading))
val listsWithMembership = _listsWithMembership.asStateFlow() val listsWithMembership = _listsWithMembership.asStateFlow()
private val _errors = Channel<Error>() private val _errors = Channel<HasListId>()
val errors = _errors.receiveAsFlow() val errors = _errors.receiveAsFlow()
private val listsWithMembershipMap = mutableMapOf<String, ListWithMembership>() private val listsWithMembershipMap = mutableMapOf<String, ListWithMembership>()
@ -164,21 +163,21 @@ class ListsForAccountViewModel @AssistedInject constructor(
* Marker for errors that can be part of the [Result] in the * Marker for errors that can be part of the [Result] in the
* [ListsForAccountViewModel.listsWithMembership] flow * [ListsForAccountViewModel.listsWithMembership] flow
*/ */
sealed interface FlowError : ApiError sealed interface FlowError : Error
/** Asynchronous errors from network operations */ /** Asynchronous errors from network operations */
sealed interface Error : ListsError { sealed interface Error : ListsError {
/** Failed to fetch lists, or lists containing a particular account */ /** Failed to fetch lists, or lists containing a particular account */
@JvmInline @JvmInline
value class GetListsWithAccount(val error: ListsError.GetListsWithAccount) : FlowError, ListsError by error value class GetListsWithAccount(private val error: ListsError.GetListsWithAccount) : FlowError, Error, ListsError by error
@JvmInline @JvmInline
value class Retrieve(val error: ListsError.Retrieve) : FlowError, ListsError by error value class Retrieve(private val error: ListsError.Retrieve) : FlowError, Error, ListsError by error
@JvmInline @JvmInline
value class AddAccounts(val error: ListsError.AddAccounts) : Error, HasListId by error, ListsError by error value class AddAccounts(private val error: ListsError.AddAccounts) : Error, HasListId by error, ListsError by error
@JvmInline @JvmInline
value class DeleteAccounts(val error: ListsError.DeleteAccounts) : Error, HasListId by error, ListsError by error value class DeleteAccounts(private val error: ListsError.DeleteAccounts) : Error, HasListId by error, ListsError by error
} }
} }

View File

@ -17,8 +17,10 @@
package app.pachli.feature.lists package app.pachli.feature.lists
import androidx.annotation.StringRes
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.data.repository.ListsError import app.pachli.core.data.repository.ListsError
import app.pachli.core.data.repository.ListsRepository import app.pachli.core.data.repository.ListsRepository
import app.pachli.core.network.model.UserListRepliesPolicy import app.pachli.core.network.model.UserListRepliesPolicy
@ -32,14 +34,20 @@ import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
sealed interface Error : ListsError { sealed class Error(
val title: String @StringRes override val resourceId: Int,
override val formatArgs: Array<out String>,
override val cause: ListsError? = null,
) : ListsError {
data class Create(override val title: String, private val error: ListsError.Create) : Error, ListsError by error data class Create(val title: String, override val cause: ListsError.Create) :
Error(R.string.error_create_list_fmt, arrayOf(title.unicodeWrap()), cause)
data class Delete(override val title: String, private val error: ListsError.Delete) : Error, ListsError by error data class Delete(val title: String, override val cause: ListsError.Delete) :
Error(R.string.error_delete_list_fmt, arrayOf(title.unicodeWrap()), cause)
data class Update(override val title: String, private val error: ListsError.Update) : Error, ListsError by error data class Update(val title: String, override val cause: ListsError.Update) :
Error(R.string.error_rename_list_fmt, arrayOf(title.unicodeWrap()), cause)
} }
@HiltViewModel @HiltViewModel