Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
275bee3c11 |
No files matched your search
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.miniabs"
|
||||
android:versionCode="2"
|
||||
android:versionName="1.1"
|
||||
android:versionCode="3"
|
||||
android:versionName="1.2"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="Settings"
|
||||
android:label="Axustes"
|
||||
android:theme="@android:style/Theme"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="keyboardHidden|orientation" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MiniABS
|
||||
|
||||
A tiny Audiobookshelf **podcast** client for the Samsung Galaxy Mini (GT-S5570) and other Android 2.2+ phones. Native Java, no libraries, ~100 KB.
|
||||
A tiny Audiobookshelf **podcast** client for the Samsung Galaxy Mini (GT-S5570) and other Android 2.2+ phones. Native Java, no libraries, ~130 KB. The interface is in Galician, with large text and touch targets sized for the Mini's 240×320 screen.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -91,6 +91,8 @@ Needs a JDK, `aapt`, `dalvik-exchange` (dx), `zipalign`, `apksigner` (Ubuntu: `a
|
||||
|
||||
Compiling against the API 8 jar guarantees no newer Android APIs are used. The APK is signed with `miniabs.keystore` (password `miniabs`); keep it to install updates over the top.
|
||||
|
||||
All sizes (text, rows, buttons, covers) are in `res/values/dimens.xml`; change them there and rebuild.
|
||||
|
||||
`tools/gen_roots.py` regenerates the bundled root certificates from a system CA store.
|
||||
|
||||
`test/run_tests.sh` runs the API, JSON, progress, HTTPS-trust and streaming-proxy code on the desktop JVM against a mock Audiobookshelf server (`test/mock_abs.py`), over plain HTTP and over TLS with good, expired, wrong-host, forged and self-signed certificates (`test/make_pki.sh`).
|
||||
|
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 650 B After Width: | Height: | Size: 994 B |
|
Before Width: | Height: | Size: 880 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 816 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 413 B |
|
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 656 B |
|
Before Width: | Height: | Size: 527 B After Width: | Height: | Size: 656 B |
|
Before Width: | Height: | Size: 377 B After Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 312 B |
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 486 B |
|
Before Width: | Height: | Size: 513 B After Width: | Height: | Size: 760 B |
|
Before Width: | Height: | Size: 656 B After Width: | Height: | Size: 834 B |
|
Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 699 B |
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="@color/accent" />
|
||||
<size android:width="14dp" android:height="14dp" />
|
||||
<stroke android:width="2dp" android:color="@color/bg" />
|
||||
<size android:width="@dimen/seek_thumb" android:height="@dimen/seek_thumb" />
|
||||
</shape>
|
||||
@@ -10,7 +10,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="MiniABS"
|
||||
android:textSize="15sp"
|
||||
android:textSize="@dimen/header_text"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent" />
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="8dp"
|
||||
android:textSize="11sp"
|
||||
android:paddingLeft="10dp"
|
||||
android:textSize="@dimen/header_sub"
|
||||
android:textColor="@color/sub"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/refresh"
|
||||
android:layout_width="36dp"
|
||||
android:layout_width="@dimen/header_h"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@drawable/row_sel"
|
||||
android:src="@drawable/ic_refresh" />
|
||||
@@ -37,12 +37,12 @@
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_height="@dimen/tab_h"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView android:id="@+id/tab_latest" style="@style/Tab" android:text="Latest" />
|
||||
<TextView android:id="@+id/tab_shows" style="@style/Tab" android:text="Shows" />
|
||||
<TextView android:id="@+id/tab_saved" style="@style/Tab" android:text="Saved" />
|
||||
<TextView android:id="@+id/tab_latest" style="@style/Tab" android:text="Últimos" />
|
||||
<TextView android:id="@+id/tab_shows" style="@style/Tab" android:text="Programas" />
|
||||
<TextView android:id="@+id/tab_saved" style="@style/Tab" android:text="Gardados" />
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/list_area" />
|
||||
|
||||
@@ -15,50 +15,50 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="4dp">
|
||||
android:layout_marginBottom="6dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_launcher" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="8dp">
|
||||
android:paddingLeft="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="MiniABS"
|
||||
android:textSize="18sp"
|
||||
android:textSize="@dimen/login_title"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Audiobookshelf podcasts"
|
||||
android:textSize="11sp"
|
||||
android:text="Podcasts de Audiobookshelf"
|
||||
android:textSize="@dimen/login_tip"
|
||||
android:textColor="@color/sub" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView style="@style/Label" android:text="Server address" />
|
||||
<TextView style="@style/Label" android:text="Enderezo do servidor" />
|
||||
<EditText
|
||||
android:id="@+id/server"
|
||||
style="@style/Field"
|
||||
android:hint="http://192.168.1.10:13378"
|
||||
android:inputType="textUri" />
|
||||
|
||||
<TextView android:id="@+id/user_label" style="@style/Label" android:text="Username" />
|
||||
<TextView android:id="@+id/user_label" style="@style/Label" android:text="Usuario" />
|
||||
<EditText
|
||||
android:id="@+id/user"
|
||||
style="@style/Field"
|
||||
android:inputType="text" />
|
||||
|
||||
<TextView android:id="@+id/pass_label" style="@style/Label" android:text="Password" />
|
||||
<TextView android:id="@+id/pass_label" style="@style/Label" android:text="Contrasinal" />
|
||||
<EditText
|
||||
android:id="@+id/pass"
|
||||
style="@style/Field"
|
||||
@@ -66,36 +66,39 @@
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/use_key"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:text="Use an API key instead" />
|
||||
android:layout_marginTop="4dp"
|
||||
android:minHeight="48dp"
|
||||
android:textSize="@dimen/login_check"
|
||||
android:text="Usar unha chave da API" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/connect"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="42dp"
|
||||
android:layout_height="@dimen/login_btn_h"
|
||||
android:layout_marginTop="6dp"
|
||||
android:background="@drawable/btn_accent"
|
||||
android:text="Connect"
|
||||
android:text="Conectar"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:textSize="@dimen/login_btn_text"
|
||||
android:textColor="#121519" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textSize="@dimen/login_status"
|
||||
android:textColor="@color/accent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="10sp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textSize="@dimen/login_tip"
|
||||
android:textColor="@color/dim"
|
||||
android:text="At home, use your server's local http:// address. For https://, Android 2.3 needs the server to offer TLS 1.0 on a separate port (see README). If the certificate isn't recognised, you'll be shown its fingerprint to check." />
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:text="Na casa, usa o enderezo local http:// do servidor. Para https://, Android 2.3 precisa que o servidor ofreza TLS 1.0 nun porto á parte (ve o README). Se o certificado non se recoñece, amosaráseche a súa pegada dixital para que a comprobes." />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -11,7 +11,9 @@
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="12sp"
|
||||
android:paddingRight="10dp"
|
||||
android:textSize="@dimen/header_sub"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
@@ -27,9 +29,9 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="84dp"
|
||||
android:layout_height="84dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_width="@dimen/cover_player"
|
||||
android:layout_height="@dimen/cover_player"
|
||||
android:layout_marginRight="10dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="@color/surface" />
|
||||
|
||||
@@ -43,7 +45,7 @@
|
||||
android:id="@+id/title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textSize="@dimen/player_title"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text"
|
||||
android:maxLines="3"
|
||||
@@ -53,8 +55,8 @@
|
||||
android:id="@+id/date"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textSize="10sp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:textSize="@dimen/player_meta"
|
||||
android:textColor="@color/sub"
|
||||
android:singleLine="true" />
|
||||
|
||||
@@ -63,9 +65,10 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textSize="10sp"
|
||||
android:textSize="@dimen/player_meta"
|
||||
android:textColor="@color/fresh"
|
||||
android:maxLines="2" />
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -73,28 +76,29 @@
|
||||
android:id="@+id/seek"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingRight="14dp"
|
||||
android:minHeight="4dp"
|
||||
android:maxHeight="4dp"
|
||||
android:paddingLeft="18dp"
|
||||
android:paddingRight="18dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:minHeight="@dimen/seek_track"
|
||||
android:maxHeight="@dimen/seek_track"
|
||||
android:progressDrawable="@drawable/seek_track"
|
||||
android:thumb="@drawable/seek_thumb"
|
||||
android:thumbOffset="7dp"
|
||||
android:thumbOffset="13dp"
|
||||
android:max="1000" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingTop="2dp">
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pos"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:textSize="11sp"
|
||||
android:textSize="@dimen/player_time"
|
||||
android:textColor="@color/sub" />
|
||||
|
||||
<TextView
|
||||
@@ -102,7 +106,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:textSize="11sp"
|
||||
android:textSize="@dimen/player_time"
|
||||
android:textColor="@color/sub" />
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -112,33 +116,33 @@
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="6dp">
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/back"
|
||||
android:layout_width="58dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_width="@dimen/skip_w"
|
||||
android:layout_height="@dimen/skip_h"
|
||||
android:background="@drawable/btn_flat"
|
||||
android:textColor="@color/text"
|
||||
android:textSize="13sp"
|
||||
android:textSize="@dimen/skip_text"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/play"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_width="@dimen/play_size"
|
||||
android:layout_height="@dimen/play_size"
|
||||
android:layout_marginLeft="14dp"
|
||||
android:layout_marginRight="14dp"
|
||||
android:background="@drawable/btn_round"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/fwd"
|
||||
android:layout_width="58dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_width="@dimen/skip_w"
|
||||
android:layout_height="@dimen/skip_h"
|
||||
android:background="@drawable/btn_flat"
|
||||
android:textColor="@color/text"
|
||||
android:textSize="13sp"
|
||||
android:textSize="@dimen/skip_text"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -154,9 +158,9 @@
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:textSize="12sp"
|
||||
android:padding="10dp"
|
||||
android:textSize="@dimen/player_desc"
|
||||
android:textColor="@color/sub"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
android:lineSpacingExtra="3dp" />
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -9,14 +9,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/header"
|
||||
android:padding="6dp"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_width="@dimen/cover_podcast"
|
||||
android:layout_height="@dimen/cover_podcast"
|
||||
android:layout_marginRight="10dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="@color/surface" />
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
android:id="@+id/title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textSize="@dimen/podcast_title"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text"
|
||||
android:maxLines="2"
|
||||
@@ -38,12 +38,8 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/sub"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
style="@style/RowMeta"
|
||||
android:layout_marginTop="2dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="13sp"
|
||||
android:textSize="@dimen/text_empty"
|
||||
android:textColor="@color/sub"
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
android:id="@+id/mini_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_height="@dimen/mini_progress"
|
||||
android:progressDrawable="@drawable/thin_progress"
|
||||
android:max="1000" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="44dp"
|
||||
android:layout_height="@dimen/mini_h"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:background="@drawable/row_sel">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mini_title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:textSize="@dimen/mini_title"
|
||||
android:textColor="@color/text"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
@@ -44,7 +44,8 @@
|
||||
android:id="@+id/mini_sub"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:textSize="@dimen/mini_sub"
|
||||
android:textColor="@color/sub"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
@@ -52,8 +53,8 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/mini_play"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_width="@dimen/mini_btn"
|
||||
android:layout_height="@dimen/mini_btn"
|
||||
android:background="@drawable/row_sel"
|
||||
android:src="@drawable/ic_play_small" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="48dp"
|
||||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp">
|
||||
android:minHeight="@dimen/row_min"
|
||||
android:paddingLeft="@dimen/row_pad"
|
||||
android:paddingRight="@dimen/row_pad"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/dot"
|
||||
android:layout_width="7dp"
|
||||
android:layout_height="7dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginRight="6dp" />
|
||||
android:layout_width="@dimen/dot"
|
||||
android:layout_height="@dimen/dot"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginRight="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
@@ -26,37 +26,23 @@
|
||||
android:id="@+id/show"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textSize="@dimen/text_show"
|
||||
android:textColor="@color/accent"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end" />
|
||||
<TextView android:id="@+id/title" style="@style/RowTitle" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:layout_height="@dimen/row_progress"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:progressDrawable="@drawable/thin_progress"
|
||||
android:max="1000" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/meta"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/sub"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
<TextView android:id="@+id/meta" style="@style/RowMeta" android:layout_marginTop="1dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -4,9 +4,10 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/surface"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textSize="10sp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:textSize="@dimen/text_section"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/sub" />
|
||||
@@ -2,7 +2,8 @@
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="44dp"
|
||||
android:layout_height="@dimen/row_min"
|
||||
android:gravity="center"
|
||||
android:textSize="13sp"
|
||||
android:textSize="@dimen/text_more"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent" />
|
||||
@@ -3,15 +3,15 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="48dp"
|
||||
android:padding="5dp"
|
||||
android:minHeight="@dimen/row_min"
|
||||
android:padding="7dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_width="@dimen/cover_row"
|
||||
android:layout_height="@dimen/cover_row"
|
||||
android:layout_marginRight="10dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="@color/surface" />
|
||||
|
||||
@@ -21,22 +21,8 @@
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end" />
|
||||
<TextView android:id="@+id/title" style="@style/RowTitle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/meta"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/sub"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
<TextView android:id="@+id/meta" style="@style/RowMeta" android:layout_marginTop="2dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="skip_labels">
|
||||
<item>5 seconds</item>
|
||||
<item>10 seconds</item>
|
||||
<item>15 seconds</item>
|
||||
<item>30 seconds</item>
|
||||
<item>45 seconds</item>
|
||||
<item>60 seconds</item>
|
||||
<item>5 segundos</item>
|
||||
<item>10 segundos</item>
|
||||
<item>15 segundos</item>
|
||||
<item>30 segundos</item>
|
||||
<item>45 segundos</item>
|
||||
<item>60 segundos</item>
|
||||
</string-array>
|
||||
<string-array name="skip_values">
|
||||
<item>5</item>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Every size in MiniABS lives here. Tuned for the Galaxy Mini (240x320, 120 dpi), where
|
||||
1dp = 0.75 px: 16sp text is 12 px tall, and 48dp touch targets are 36 px (about 7 mm).
|
||||
-->
|
||||
<resources>
|
||||
<!-- top bar and tabs -->
|
||||
<dimen name="header_h">46dp</dimen>
|
||||
<dimen name="header_text">19sp</dimen>
|
||||
<dimen name="header_sub">14sp</dimen>
|
||||
<dimen name="tab_h">46dp</dimen>
|
||||
<dimen name="tab_text">16sp</dimen>
|
||||
|
||||
<!-- lists -->
|
||||
<dimen name="row_min">66dp</dimen>
|
||||
<dimen name="row_pad">9dp</dimen>
|
||||
<dimen name="text_title">16sp</dimen>
|
||||
<dimen name="text_meta">13sp</dimen>
|
||||
<dimen name="text_show">13sp</dimen>
|
||||
<dimen name="text_section">13sp</dimen>
|
||||
<dimen name="text_more">17sp</dimen>
|
||||
<dimen name="text_empty">16sp</dimen>
|
||||
<dimen name="dot">11dp</dimen>
|
||||
<dimen name="row_progress">4dp</dimen>
|
||||
<dimen name="cover_row">54dp</dimen>
|
||||
|
||||
<!-- show header -->
|
||||
<dimen name="cover_podcast">62dp</dimen>
|
||||
<dimen name="podcast_title">17sp</dimen>
|
||||
|
||||
<!-- mini player bar -->
|
||||
<dimen name="mini_h">60dp</dimen>
|
||||
<dimen name="mini_title">15sp</dimen>
|
||||
<dimen name="mini_sub">13sp</dimen>
|
||||
<dimen name="mini_btn">60dp</dimen>
|
||||
<dimen name="mini_progress">3dp</dimen>
|
||||
|
||||
<!-- player -->
|
||||
<dimen name="cover_player">100dp</dimen>
|
||||
<dimen name="player_title">17sp</dimen>
|
||||
<dimen name="player_meta">13sp</dimen>
|
||||
<dimen name="player_time">15sp</dimen>
|
||||
<dimen name="player_desc">15sp</dimen>
|
||||
<dimen name="skip_w">82dp</dimen>
|
||||
<dimen name="skip_h">56dp</dimen>
|
||||
<dimen name="skip_text">20sp</dimen>
|
||||
<dimen name="play_size">78dp</dimen>
|
||||
<dimen name="seek_thumb">26dp</dimen>
|
||||
<dimen name="seek_track">7dp</dimen>
|
||||
|
||||
<!-- login -->
|
||||
<dimen name="login_title">24sp</dimen>
|
||||
<dimen name="login_label">15sp</dimen>
|
||||
<dimen name="login_field">18sp</dimen>
|
||||
<dimen name="login_field_h">52dp</dimen>
|
||||
<dimen name="login_check">16sp</dimen>
|
||||
<dimen name="login_btn_h">56dp</dimen>
|
||||
<dimen name="login_btn_text">20sp</dimen>
|
||||
<dimen name="login_status">15sp</dimen>
|
||||
<dimen name="login_tip">13sp</dimen>
|
||||
</resources>
|
||||
@@ -7,8 +7,8 @@
|
||||
<color name="header">#0B0D10</color>
|
||||
<color name="line">#2A3038</color>
|
||||
<color name="text">#ECEFF2</color>
|
||||
<color name="sub">#9AA4AE</color>
|
||||
<color name="dim">#5C6670</color>
|
||||
<color name="sub">#A9B2BC</color>
|
||||
<color name="dim">#6E7882</color>
|
||||
<color name="accent">#F5A623</color>
|
||||
<color name="fresh">#6EC1E4</color>
|
||||
<color name="pressed">#2C333C</color>
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
<style name="Header">
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">32dp</item>
|
||||
<item name="android:layout_height">@dimen/header_h</item>
|
||||
<item name="android:background">@color/header</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:paddingLeft">8dp</item>
|
||||
<item name="android:paddingRight">2dp</item>
|
||||
<item name="android:paddingLeft">10dp</item>
|
||||
<item name="android:paddingRight">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Tab">
|
||||
@@ -34,9 +34,10 @@
|
||||
<item name="android:layout_height">fill_parent</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:textSize">13sp</item>
|
||||
<item name="android:textSize">@dimen/tab_text</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textColor">@color/sub</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:background">@drawable/tab_bg</item>
|
||||
</style>
|
||||
|
||||
@@ -53,7 +54,8 @@
|
||||
<style name="Field">
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:minHeight">@dimen/login_field_h</item>
|
||||
<item name="android:textSize">@dimen/login_field</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:textColor">#000000</item>
|
||||
</style>
|
||||
@@ -61,8 +63,27 @@
|
||||
<style name="Label">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textSize">11sp</item>
|
||||
<item name="android:textSize">@dimen/login_label</item>
|
||||
<item name="android:textColor">@color/sub</item>
|
||||
<item name="android:layout_marginTop">6dp</item>
|
||||
<item name="android:layout_marginTop">8dp</item>
|
||||
<item name="android:layout_marginBottom">2dp</item>
|
||||
</style>
|
||||
|
||||
<style name="RowTitle">
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textSize">@dimen/text_title</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
<item name="android:maxLines">2</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
</style>
|
||||
|
||||
<style name="RowMeta">
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textSize">@dimen/text_meta</item>
|
||||
<item name="android:textColor">@color/sub</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,56 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory android:title="Playback">
|
||||
<PreferenceCategory android:title="Reprodución">
|
||||
<ListPreference
|
||||
android:key="skip_back"
|
||||
android:title="Skip back"
|
||||
android:title="Retroceder"
|
||||
android:entries="@array/skip_labels"
|
||||
android:entryValues="@array/skip_values"
|
||||
android:defaultValue="15" />
|
||||
<ListPreference
|
||||
android:key="skip_fwd"
|
||||
android:title="Skip forward"
|
||||
android:title="Avanzar"
|
||||
android:entries="@array/skip_labels"
|
||||
android:entryValues="@array/skip_values"
|
||||
android:defaultValue="30" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Downloads">
|
||||
<PreferenceCategory android:title="Descargas">
|
||||
<CheckBoxPreference
|
||||
android:key="wifi_only"
|
||||
android:title="Download on Wi-Fi only"
|
||||
android:summary="Saves your mobile data"
|
||||
android:title="Descargar só con wifi"
|
||||
android:summary="Aforra datos móbiles"
|
||||
android:defaultValue="true" />
|
||||
<Preference
|
||||
android:key="delete_all"
|
||||
android:title="Delete all downloads" />
|
||||
android:title="Eliminar todas as descargas" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Display">
|
||||
<PreferenceCategory android:title="Aparencia">
|
||||
<CheckBoxPreference
|
||||
android:key="covers"
|
||||
android:title="Show cover art"
|
||||
android:summary="Turn off to save data and memory"
|
||||
android:title="Amosar portadas"
|
||||
android:summary="Desactívao para aforrar datos e memoria"
|
||||
android:defaultValue="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Account">
|
||||
<PreferenceCategory android:title="Conta">
|
||||
<Preference
|
||||
android:key="account"
|
||||
android:title="Signed in" />
|
||||
android:title="Sesión iniciada" />
|
||||
<Preference
|
||||
android:key="pins"
|
||||
android:title="Certificates you trusted" />
|
||||
android:title="Certificados nos que confías" />
|
||||
<Preference
|
||||
android:key="logout"
|
||||
android:title="Log out" />
|
||||
android:title="Pechar sesión" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="About">
|
||||
<PreferenceCategory android:title="Acerca de">
|
||||
<Preference
|
||||
android:key="about"
|
||||
android:title="MiniABS 1.1"
|
||||
android:summary="A tiny Audiobookshelf podcast client for Android 2.2+" />
|
||||
android:title="MiniABS 1.2"
|
||||
android:summary="Un pequeno cliente de podcasts de Audiobookshelf para Android 2.2+" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
@@ -26,7 +26,7 @@ import android.os.Build;
|
||||
|
||||
/** Audiobookshelf REST client (API as of server 2.x, including the 2.26+ token system). */
|
||||
final class Api {
|
||||
static final String VERSION = "1.1";
|
||||
static final String VERSION = "1.2";
|
||||
|
||||
static class ApiException extends IOException {
|
||||
final int code;
|
||||
@@ -100,7 +100,7 @@ final class Api {
|
||||
if (peer.length == 0 || !(peer[0] instanceof java.security.cert.X509Certificate)
|
||||
|| !Trust.hostMatches((java.security.cert.X509Certificate) peer[0], u.getHost())) {
|
||||
ss.close();
|
||||
throw new SSLException("Certificate doesn't match " + u.getHost());
|
||||
throw new SSLException("O certificado non corresponde a " + u.getHost());
|
||||
}
|
||||
io = ss;
|
||||
}
|
||||
@@ -123,11 +123,11 @@ final class Api {
|
||||
int ch;
|
||||
while ((ch = in.read()) != -1 && ch != '\n' && line.length() < 200) line.append((char) ch);
|
||||
String[] parts = line.toString().trim().split(" ");
|
||||
if (parts.length < 2 || !parts[0].startsWith("HTTP/")) throw new IOException("Unexpected reply from server");
|
||||
if (parts.length < 2 || !parts[0].startsWith("HTTP/")) throw new IOException("Resposta inesperada do servidor");
|
||||
try {
|
||||
code = Integer.parseInt(parts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
byte[] rest = new byte[1024];
|
||||
int total = 0, n;
|
||||
@@ -144,10 +144,10 @@ final class Api {
|
||||
refreshIf(c, used, 0);
|
||||
return rawPatch(c, path, body, false);
|
||||
}
|
||||
throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
}
|
||||
if (code >= 400) throw new ApiException(code, "");
|
||||
if (code >= 300) throw new ApiException(code, "The server redirected; check the address in Settings.");
|
||||
if (code >= 300) throw new ApiException(code, "O servidor redirixiu a petición; comproba o enderezo do servidor.");
|
||||
return "OK";
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ final class Api {
|
||||
static Object call(Context c, String method, String path, JSONObject body, int maxString, String[] deepSkip,
|
||||
boolean mayRefresh) throws IOException {
|
||||
Prefs p = Prefs.get(c);
|
||||
if (!p.loggedIn()) throw new AuthException("Please log in.");
|
||||
if (!p.loggedIn()) throw new AuthException("Inicia sesión.");
|
||||
if ("PATCH".equals(method)) return rawPatch(c, path, body, mayRefresh);
|
||||
if (mayRefresh) ensureFresh(c, 120);
|
||||
String used = p.token();
|
||||
@@ -232,7 +232,7 @@ final class Api {
|
||||
refreshIf(c, used, 0);
|
||||
return call(c, method, path, body, maxString, deepSkip, false);
|
||||
}
|
||||
throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
}
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
return readBody(conn, maxString, deepSkip);
|
||||
@@ -243,7 +243,7 @@ final class Api {
|
||||
|
||||
static JSONObject obj(Object o) throws IOException {
|
||||
if (o instanceof JSONObject) return (JSONObject) o;
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ sign-in
|
||||
@@ -253,13 +253,13 @@ final class Api {
|
||||
HttpURLConnection conn = open(c, server + "/status", "GET");
|
||||
try {
|
||||
int code = responseCode(conn);
|
||||
if (code == 404) throw new IOException("No Audiobookshelf server at this address.");
|
||||
if (code == 404) throw new IOException("Non hai ningún servidor de Audiobookshelf neste enderezo.");
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
Object o = readBody(conn, 200, null);
|
||||
if (!(o instanceof JSONObject)) throw new IOException("No Audiobookshelf server at this address.");
|
||||
if (!(o instanceof JSONObject)) throw new IOException("Non hai ningún servidor de Audiobookshelf neste enderezo.");
|
||||
JSONObject s = (JSONObject) o;
|
||||
if (s.has("isInit") && !s.optBoolean("isInit", true)) {
|
||||
throw new IOException("This server hasn't been set up yet. Finish setup in a browser first.");
|
||||
throw new IOException("Este servidor aínda non está configurado. Remata a configuración nun navegador.");
|
||||
}
|
||||
return Util.str(s, "serverVersion");
|
||||
} finally {
|
||||
@@ -280,19 +280,19 @@ final class Api {
|
||||
}
|
||||
writeBody(conn, body);
|
||||
int code = responseCode(conn);
|
||||
if (code == 401) throw new ApiException(401, "Wrong username or password.");
|
||||
if (code == 429) throw new ApiException(429, "Too many attempts. Wait a minute and try again.");
|
||||
if (code == 401) throw new ApiException(401, "Usuario ou contrasinal incorrectos.");
|
||||
if (code == 429) throw new ApiException(429, "Demasiados intentos. Agarda un minuto e téntao de novo.");
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
JSONObject o = obj(readBody(conn, 500, null));
|
||||
JSONObject u = o.optJSONObject("user");
|
||||
if (u == null) throw new IOException("Unexpected reply from server");
|
||||
if (u == null) throw new IOException("Resposta inesperada do servidor");
|
||||
String access = Util.str(u, "accessToken");
|
||||
String refresh = Util.str(u, "refreshToken");
|
||||
String legacy = Util.str(u, "token");
|
||||
Prefs p = Prefs.get(c);
|
||||
if (access.length() > 0) p.setAuth(user, Prefs.MODE_JWT, access, refresh);
|
||||
else if (legacy.length() > 0) p.setAuth(user, Prefs.MODE_LEGACY, legacy, "");
|
||||
else throw new IOException("The server didn't return a sign-in token.");
|
||||
else throw new IOException("O servidor non devolveu ningún token de sesión.");
|
||||
return Util.str(o, "userDefaultLibraryId");
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
@@ -310,7 +310,7 @@ final class Api {
|
||||
return name;
|
||||
} catch (IOException e) {
|
||||
p.logout();
|
||||
if (e instanceof AuthException) throw new ApiException(401, "That API key wasn't accepted.");
|
||||
if (e instanceof AuthException) throw new ApiException(401, "Non se aceptou esa chave da API.");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ final class Api {
|
||||
private static void refreshIf(Context c, String stale, long minSeconds) throws IOException {
|
||||
synchronized (REFRESH_LOCK) {
|
||||
Prefs p = Prefs.get(c);
|
||||
if (!p.canRefresh()) throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
if (!p.canRefresh()) throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
if (stale != null) {
|
||||
if (!stale.equals(p.token())) return;
|
||||
} else if (p.tokenSecondsLeft() >= minSeconds) {
|
||||
@@ -348,12 +348,12 @@ final class Api {
|
||||
writeBody(conn, null);
|
||||
int code = responseCode(conn);
|
||||
if (code == 401 || code == 403) {
|
||||
throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
}
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
JSONObject u = obj(readBody(conn, 500, null)).optJSONObject("user");
|
||||
String access = u == null ? "" : Util.str(u, "accessToken");
|
||||
if (access.length() == 0) throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
if (access.length() == 0) throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
p.setTokens(access, Util.str(u, "refreshToken"));
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
|
||||
@@ -73,7 +73,7 @@ abstract class BaseActivity extends Activity {
|
||||
return TrustPrompt.offer(this, new Runnable() {
|
||||
public void run() {
|
||||
if (retry != null) retry.run();
|
||||
else toast("Certificate trusted. Try again.");
|
||||
else toast("Certificado aceptado. Téntao de novo.");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -130,13 +130,13 @@ abstract class BaseActivity extends Activity {
|
||||
dur = ps.durationMs();
|
||||
int st = ps.state();
|
||||
if (st == PlayerService.LOADING || st == PlayerService.ERROR) sub = ps.status();
|
||||
else sub = Util.join(e.show, dur > 0 ? Util.clock(dur - pos) + " left" : "");
|
||||
else sub = Util.join(e.show, dur > 0 ? "quedan " + Util.clock(dur - pos) : "");
|
||||
} else {
|
||||
double[] p = App.progress(e.id);
|
||||
double cur = p != null ? p[0] : Math.max(0, App.localPosition(this, e.id));
|
||||
pos = (int) (cur * 1000);
|
||||
dur = (int) (e.duration * 1000);
|
||||
sub = Util.join(e.show, "Paused");
|
||||
sub = Util.join(e.show, "En pausa");
|
||||
}
|
||||
miniSub.setText(sub);
|
||||
miniProgress.setProgress(dur > 0 ? (int) (1000L * pos / dur) : 0);
|
||||
@@ -149,14 +149,14 @@ abstract class BaseActivity extends Activity {
|
||||
|
||||
protected void episodeMenu(ContextMenu menu, Episode e, boolean offerShow) {
|
||||
menu.setHeaderTitle(e.title);
|
||||
menu.add(0, M_PLAY, 0, "Play");
|
||||
if (DownloadService.busyWith(e.id)) menu.add(0, M_CANCEL, 0, "Cancel download");
|
||||
else if (Downloads.has(this, e.id)) menu.add(0, M_DELETE, 0, "Delete download");
|
||||
else menu.add(0, M_DOWNLOAD, 0, "Download");
|
||||
menu.add(0, M_PLAY, 0, "Reproducir");
|
||||
if (DownloadService.busyWith(e.id)) menu.add(0, M_CANCEL, 0, "Cancelar descarga");
|
||||
else if (Downloads.has(this, e.id)) menu.add(0, M_DELETE, 0, "Eliminar descarga");
|
||||
else menu.add(0, M_DOWNLOAD, 0, "Descargar");
|
||||
double[] p = App.progress(e.id);
|
||||
if (p != null && p[2] > 0) menu.add(0, M_UNPLAYED, 0, "Mark as unplayed");
|
||||
else menu.add(0, M_PLAYED, 0, "Mark as played");
|
||||
if (offerShow) menu.add(0, M_SHOW, 0, "Go to show");
|
||||
if (p != null && p[2] > 0) menu.add(0, M_UNPLAYED, 0, "Marcar como non escoitado");
|
||||
else menu.add(0, M_PLAYED, 0, "Marcar como escoitado");
|
||||
if (offerShow) menu.add(0, M_SHOW, 0, "Ir ao programa");
|
||||
}
|
||||
|
||||
/** Returns true if handled. onChanged runs on the UI thread after the list should refresh. */
|
||||
@@ -168,26 +168,26 @@ abstract class BaseActivity extends Activity {
|
||||
case M_DOWNLOAD:
|
||||
if (Prefs.get(this).wifiOnly() && !Util.onWifi(this)) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage("You're not on Wi-Fi. Download over mobile data this time?")
|
||||
.setPositiveButton("Download", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Non estás nunha wifi. Descargar con datos móbiles esta vez?")
|
||||
.setPositiveButton("Descargar", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
Prefs.get(BaseActivity.this).sp.edit().putBoolean("wifi_only", false).commit();
|
||||
DownloadService.enqueue(BaseActivity.this, e);
|
||||
toast("Downloading. Wi-Fi-only is now off (change it in Settings).");
|
||||
toast("Descargando. Desactivouse «só con wifi» (podes cambialo en Axustes).");
|
||||
if (onChanged != null) onChanged.run();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setNegativeButton("Cancelar", null)
|
||||
.show();
|
||||
} else {
|
||||
DownloadService.enqueue(this, e);
|
||||
toast("Downloading…");
|
||||
toast("Descargando…");
|
||||
}
|
||||
if (onChanged != null) onChanged.run();
|
||||
return true;
|
||||
case M_CANCEL:
|
||||
DownloadService.cancel(e.id);
|
||||
toast("Download cancelled");
|
||||
toast("Descarga cancelada");
|
||||
if (onChanged != null) onChanged.run();
|
||||
return true;
|
||||
case M_DELETE:
|
||||
@@ -196,7 +196,7 @@ abstract class BaseActivity extends Activity {
|
||||
ps.stop();
|
||||
}
|
||||
Downloads.delete(this, e);
|
||||
toast("Download deleted");
|
||||
toast("Descarga eliminada");
|
||||
if (onChanged != null) onChanged.run();
|
||||
return true;
|
||||
case M_PLAYED:
|
||||
@@ -230,7 +230,7 @@ abstract class BaseActivity extends Activity {
|
||||
}
|
||||
|
||||
void done(Void r) {
|
||||
toast(played ? "Marked as played" : "Marked as unplayed");
|
||||
toast(played ? "Marcado como escoitado" : "Marcado como non escoitado");
|
||||
}
|
||||
|
||||
void failed(Exception ex) {
|
||||
|
||||
@@ -102,7 +102,7 @@ public class DownloadService extends IntentService {
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!e.id.equals(cancelId)) {
|
||||
lastError = "Download failed: " + Util.describe(ex);
|
||||
lastError = "Fallou a descarga: " + Util.describe(ex);
|
||||
notifyDone(e.title, lastError);
|
||||
}
|
||||
} finally {
|
||||
@@ -116,14 +116,14 @@ public class DownloadService extends IntentService {
|
||||
|
||||
private void download(Episode e) throws IOException {
|
||||
if (Prefs.get(this).wifiOnly() && !Util.onWifi(this)) {
|
||||
throw new IOException("not on Wi-Fi (Settings: Download on Wi-Fi only)");
|
||||
throw new IOException("non hai wifi (Axustes: Descargar só con wifi)");
|
||||
}
|
||||
File dir = Downloads.dir(this);
|
||||
if (Downloads.audio(this, e) != null) {
|
||||
Downloads.saved(this, e);
|
||||
return;
|
||||
}
|
||||
if (e.ino.length() == 0) throw new IOException("this episode has no audio file");
|
||||
if (e.ino.length() == 0) throw new IOException("este episodio non ten ficheiro de audio");
|
||||
File part = new File(dir, e.id + ".part");
|
||||
Notification n = progressNotification(e, 0);
|
||||
startForeground(NOTIF_ID, n);
|
||||
@@ -158,8 +158,8 @@ public class DownloadService extends IntentService {
|
||||
if (code != 404 && code != 403) break;
|
||||
}
|
||||
if (code != 200 && code != 206) {
|
||||
if (code == 401) throw new Api.AuthException("sign-in expired; log in again");
|
||||
if (code == 403) throw new IOException("your account isn't allowed to download");
|
||||
if (code == 401) throw new Api.AuthException("a sesión caducou; inicia sesión de novo");
|
||||
if (code == 403) throw new IOException("a túa conta non ten permiso para descargar");
|
||||
throw new Api.ApiException(code, "");
|
||||
}
|
||||
try {
|
||||
@@ -194,7 +194,7 @@ public class DownloadService extends IntentService {
|
||||
}
|
||||
if (e.id.equals(cancelId)) return;
|
||||
File dest = new File(dir, e.id + e.ext);
|
||||
if (!part.renameTo(dest)) throw new IOException("couldn't save the file");
|
||||
if (!part.renameTo(dest)) throw new IOException("non se puido gardar o ficheiro");
|
||||
Downloads.saved(this, e);
|
||||
percent = 100;
|
||||
}
|
||||
@@ -216,7 +216,7 @@ public class DownloadService extends IntentService {
|
||||
synchronized (queue) {
|
||||
left = queue.size();
|
||||
}
|
||||
String text = "Downloading " + pct + "%" + (left > 0 ? " · " + left + " more queued" : "");
|
||||
String text = "Descargando " + pct + " %" + (left > 0 ? " · " + left + " máis na cola" : "");
|
||||
n.setLatestEventInfo(this, e.title, text, openApp());
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ final class Downloads {
|
||||
|
||||
static File dir(Context c) throws IOException {
|
||||
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
throw new IOException("No SD card available. Insert or unmount it from USB to download.");
|
||||
throw new IOException("Non hai tarxeta SD dispoñible. Insírea ou desconéctaa do USB para descargar.");
|
||||
}
|
||||
File d = c.getExternalFilesDir(Environment.DIRECTORY_PODCASTS);
|
||||
if (d == null) throw new IOException("Can't use the SD card.");
|
||||
if (!d.exists() && !d.mkdirs()) throw new IOException("Can't create a folder on the SD card.");
|
||||
if (d == null) throw new IOException("Non se pode usar a tarxeta SD.");
|
||||
if (!d.exists() && !d.mkdirs()) throw new IOException("Non se pode crear un cartafol na tarxeta SD.");
|
||||
File nomedia = new File(d, ".nomedia");
|
||||
if (!nomedia.exists()) {
|
||||
try {
|
||||
|
||||
@@ -202,7 +202,7 @@ public class HomeActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
void done(Integer n) {
|
||||
if (n > 0) toast("Synced offline progress");
|
||||
if (n > 0) toast("Sincronizouse o progreso gardado sen conexión");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
@@ -222,7 +222,7 @@ public class HomeActivity extends BaseActivity {
|
||||
found.add(new String[]{Util.str(l, "id"), Util.str(l, "name")});
|
||||
}
|
||||
}
|
||||
if (found.isEmpty()) throw new IOException("This server has no podcast libraries.");
|
||||
if (found.isEmpty()) throw new IOException("Este servidor non ten bibliotecas de podcasts.");
|
||||
podcastLibs = found;
|
||||
}
|
||||
String id = p.libraryId();
|
||||
@@ -273,10 +273,10 @@ public class HomeActivity extends BaseActivity {
|
||||
// older servers: just skip this section
|
||||
}
|
||||
if (!cont.isEmpty()) {
|
||||
rows.add(Rows.Row.header("CONTINUE LISTENING"));
|
||||
rows.add(Rows.Row.header("CONTINUAR ESCOITANDO"));
|
||||
rows.addAll(cont);
|
||||
}
|
||||
rows.add(Rows.Row.header("LATEST EPISODES"));
|
||||
rows.add(Rows.Row.header("ÚLTIMOS EPISODIOS"));
|
||||
}
|
||||
JSONArray eps = Api.recentEpisodes(HomeActivity.this, lib, page, PAGE).optJSONArray("episodes");
|
||||
int n = eps == null ? 0 : eps.length();
|
||||
@@ -301,8 +301,8 @@ public class HomeActivity extends BaseActivity {
|
||||
}
|
||||
latestPage = page;
|
||||
latestMore = hasMore;
|
||||
if (hasMore) latestCache.add(Rows.Row.more("Load more"));
|
||||
if (tab == TAB_LATEST) show(latestCache, "No episodes yet.");
|
||||
if (hasMore) latestCache.add(Rows.Row.more("Cargar máis"));
|
||||
if (tab == TAB_LATEST) show(latestCache, "Aínda non hai episodios.");
|
||||
}
|
||||
|
||||
void failed(Exception e) {
|
||||
@@ -315,12 +315,12 @@ public class HomeActivity extends BaseActivity {
|
||||
if (more) {
|
||||
toast(Util.describe(e));
|
||||
if (latestCache != null && !latestCache.isEmpty()) {
|
||||
latestCache.get(latestCache.size() - 1).text = "Load more";
|
||||
latestCache.get(latestCache.size() - 1).text = "Cargar máis";
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
latestError = Util.describe(e) + "\n\nTap refresh to try again.";
|
||||
latestError = Util.describe(e) + "\n\nToca o botón de actualizar (arriba á dereita) para tentalo de novo.";
|
||||
if (offerTrust(retryRefresh)) {
|
||||
if (tab == TAB_LATEST) show(null, null);
|
||||
return;
|
||||
@@ -368,7 +368,7 @@ public class HomeActivity extends BaseActivity {
|
||||
if (isFinishing()) return;
|
||||
updateLibraryLabel();
|
||||
showsCache = rows;
|
||||
if (tab == TAB_SHOWS) show(rows, "No podcasts in this library.");
|
||||
if (tab == TAB_SHOWS) show(rows, "Non hai podcasts nesta biblioteca.");
|
||||
}
|
||||
|
||||
void failed(Exception e) {
|
||||
@@ -378,7 +378,7 @@ public class HomeActivity extends BaseActivity {
|
||||
showError(e);
|
||||
return;
|
||||
}
|
||||
showsError = Util.describe(e) + "\n\nTap refresh to try again.";
|
||||
showsError = Util.describe(e) + "\n\nToca o botón de actualizar (arriba á dereita) para tentalo de novo.";
|
||||
if (offerTrust(retryRefresh)) {
|
||||
if (tab == TAB_SHOWS) show(null, null);
|
||||
return;
|
||||
@@ -391,7 +391,7 @@ public class HomeActivity extends BaseActivity {
|
||||
/** When the server can't be reached but episodes are saved, show those instead. */
|
||||
private void offlineFallback(Exception e) {
|
||||
if (!(e instanceof Api.ApiException) && !Downloads.list(this).isEmpty()) {
|
||||
toast("Can't reach the server. Showing saved episodes.");
|
||||
toast("Non se pode acceder ao servidor. Amósanse os episodios gardados.");
|
||||
selectTab(TAB_SAVED);
|
||||
} else {
|
||||
show(null, null);
|
||||
@@ -405,17 +405,17 @@ public class HomeActivity extends BaseActivity {
|
||||
savedSignature = (cur == null ? "" : cur.id) + "/" + queued.size() + "/" + DownloadService.lastError;
|
||||
if (DownloadService.lastError != null) rows.add(Rows.Row.header(DownloadService.lastError));
|
||||
if (cur != null || !queued.isEmpty()) {
|
||||
rows.add(Rows.Row.header("DOWNLOADING"));
|
||||
rows.add(Rows.Row.header("DESCARGANDO"));
|
||||
if (cur != null) rows.add(Rows.Row.episode(cur));
|
||||
for (Episode q : queued) rows.add(Rows.Row.episode(q));
|
||||
}
|
||||
List<Episode> saved = Downloads.list(this);
|
||||
if (!saved.isEmpty()) {
|
||||
rows.add(Rows.Row.header("SAVED ON SD CARD (" + saved.size() + ")"));
|
||||
rows.add(Rows.Row.header("GARDADOS NA TARXETA SD (" + saved.size() + ")"));
|
||||
for (Episode e : saved) rows.add(Rows.Row.episode(e));
|
||||
}
|
||||
if (rows.isEmpty()) {
|
||||
show(rows, "Nothing saved yet.\n\nLong-press an episode and choose Download to listen offline.");
|
||||
show(rows, "Aínda non gardaches nada.\n\nMantén premido un episodio e escolle Descargar para escoitalo sen conexión.");
|
||||
} else {
|
||||
show(rows, null);
|
||||
}
|
||||
@@ -436,7 +436,7 @@ public class HomeActivity extends BaseActivity {
|
||||
break;
|
||||
case Rows.MORE:
|
||||
if (!loadingLatest && latestMore) {
|
||||
r.text = "Loading…";
|
||||
r.text = "Cargando…";
|
||||
adapter.notifyDataSetChanged();
|
||||
loadLatest(true);
|
||||
}
|
||||
@@ -469,12 +469,12 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
private void chooseLibrary() {
|
||||
if (podcastLibs == null || podcastLibs.size() < 2) {
|
||||
toast(podcastLibs == null ? "Still loading libraries…" : "This is your only podcast library.");
|
||||
toast(podcastLibs == null ? "Aínda se están cargando as bibliotecas…" : "Esta é a túa única biblioteca de podcasts.");
|
||||
return;
|
||||
}
|
||||
String[] names = new String[podcastLibs.size()];
|
||||
for (int i = 0; i < names.length; i++) names[i] = podcastLibs.get(i)[1];
|
||||
new AlertDialog.Builder(this).setTitle("Podcast library")
|
||||
new AlertDialog.Builder(this).setTitle("Biblioteca de podcasts")
|
||||
.setItems(names, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
String[] l = podcastLibs.get(which);
|
||||
@@ -491,10 +491,10 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, 1, 0, "Refresh").setIcon(android.R.drawable.ic_menu_rotate);
|
||||
menu.add(0, 2, 0, "Library").setIcon(android.R.drawable.ic_menu_agenda);
|
||||
menu.add(0, 3, 0, "Now playing").setIcon(android.R.drawable.ic_media_play);
|
||||
menu.add(0, 4, 0, "Settings").setIcon(android.R.drawable.ic_menu_preferences);
|
||||
menu.add(0, 1, 0, "Actualizar").setIcon(android.R.drawable.ic_menu_rotate);
|
||||
menu.add(0, 2, 0, "Biblioteca").setIcon(android.R.drawable.ic_menu_agenda);
|
||||
menu.add(0, 3, 0, "En reprodución").setIcon(android.R.drawable.ic_media_play);
|
||||
menu.add(0, 4, 0, "Axustes").setIcon(android.R.drawable.ic_menu_preferences);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,13 +88,13 @@ final class Json {
|
||||
return JSONObject.NULL;
|
||||
default:
|
||||
if (c == '-' || (c >= '0' && c <= '9')) return number(c);
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
private void expect(String rest) throws IOException {
|
||||
for (int i = 0; i < rest.length(); i++) {
|
||||
if (read() != rest.charAt(i)) throw new IOException("Unexpected reply from server");
|
||||
if (read() != rest.charAt(i)) throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,10 +149,10 @@ final class Json {
|
||||
return o;
|
||||
}
|
||||
while (true) {
|
||||
if (c != '"') throw new IOException("Unexpected reply from server");
|
||||
if (c != '"') throw new IOException("Resposta inesperada do servidor");
|
||||
String key = string(512);
|
||||
c = skipWs();
|
||||
if (c != ':') throw new IOException("Unexpected reply from server");
|
||||
if (c != ':') throw new IOException("Resposta inesperada do servidor");
|
||||
c = skipWs();
|
||||
if (skipKey(key)) {
|
||||
skipValue(c);
|
||||
@@ -173,7 +173,7 @@ final class Json {
|
||||
depth--;
|
||||
return o;
|
||||
}
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ final class Json {
|
||||
continue;
|
||||
}
|
||||
if (c == ']') return a;
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ final class Json {
|
||||
int d = 1;
|
||||
while (d > 0) {
|
||||
int ch = read();
|
||||
if (ch == -1) throw new IOException("Reply was cut off");
|
||||
if (ch == -1) throw new IOException("A resposta chegou incompleta");
|
||||
if (ch == '"') string(0);
|
||||
else if (ch == '{' || ch == '[') d++;
|
||||
else if (ch == '}' || ch == ']') d--;
|
||||
@@ -217,7 +217,7 @@ final class Json {
|
||||
boolean cut = false;
|
||||
while (true) {
|
||||
int c = read();
|
||||
if (c == -1) throw new IOException("Reply was cut off");
|
||||
if (c == -1) throw new IOException("A resposta chegou incompleta");
|
||||
if (c == '"') break;
|
||||
if (c == '\\') {
|
||||
c = read();
|
||||
@@ -231,14 +231,14 @@ final class Json {
|
||||
int v = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int d = Character.digit(read(), 16);
|
||||
if (d < 0) throw new IOException("Unexpected reply from server");
|
||||
if (d < 0) throw new IOException("Resposta inesperada do servidor");
|
||||
v = v * 16 + d;
|
||||
}
|
||||
c = v;
|
||||
break;
|
||||
}
|
||||
case -1:
|
||||
throw new IOException("Reply was cut off");
|
||||
throw new IOException("A resposta chegou incompleta");
|
||||
default:
|
||||
break; // \" \\ \/
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class LoginActivity extends Activity {
|
||||
int vis = on ? View.GONE : View.VISIBLE;
|
||||
user.setVisibility(vis);
|
||||
userLabel.setVisibility(vis);
|
||||
passLabel.setText(on ? "API key" : "Password");
|
||||
passLabel.setText(on ? "Chave da API" : "Contrasinal");
|
||||
pass.setInputType(on ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
: InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
pass.setText("");
|
||||
@@ -69,17 +69,17 @@ public class LoginActivity extends Activity {
|
||||
final String pw = pass.getText().toString();
|
||||
final boolean key = useKey.isChecked();
|
||||
if (srv.length() == 0) {
|
||||
status.setText("Enter your server address.");
|
||||
status.setText("Escribe o enderezo do servidor.");
|
||||
return;
|
||||
}
|
||||
if (key ? pw.trim().length() == 0 : u.length() == 0) {
|
||||
status.setText(key ? "Paste your API key." : "Enter your username.");
|
||||
status.setText(key ? "Pega a túa chave da API." : "Escribe o teu usuario.");
|
||||
return;
|
||||
}
|
||||
server.setText(srv);
|
||||
((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(pass.getWindowToken(), 0);
|
||||
connect.setEnabled(false);
|
||||
status.setText("Connecting…");
|
||||
status.setText("Conectando…");
|
||||
|
||||
final Prefs p = Prefs.get(this);
|
||||
p.logout();
|
||||
|
||||
@@ -167,9 +167,9 @@ public class PlayerActivity extends BaseActivity {
|
||||
Episode e = current();
|
||||
if (e == null) {
|
||||
show.setText("");
|
||||
title.setText("Nothing playing");
|
||||
title.setText("Non se está a reproducir nada");
|
||||
date.setText("");
|
||||
status.setText("Pick an episode from the list.");
|
||||
status.setText("Escolle un episodio da lista.");
|
||||
desc.setText("");
|
||||
cover.setVisibility(View.GONE);
|
||||
setControls(false);
|
||||
@@ -181,7 +181,7 @@ public class PlayerActivity extends BaseActivity {
|
||||
title.setText(e.title);
|
||||
date.setText(Util.join(Util.date(e.published), e.duration > 0 ? Util.duration(e.duration) : ""));
|
||||
desc.setText(Util.plainText(e.desc));
|
||||
Covers.load(this, cover, e.itemId, (int) (84 * getResources().getDisplayMetrics().density));
|
||||
Covers.load(this, cover, e.itemId, getResources().getDimensionPixelSize(R.dimen.cover_player));
|
||||
}
|
||||
|
||||
PlayerService ps = PlayerService.get();
|
||||
@@ -195,8 +195,8 @@ public class PlayerActivity extends BaseActivity {
|
||||
playing = ps.isPlaying() || (ps.state() == PlayerService.LOADING);
|
||||
st = ps.status();
|
||||
long sleep = ps.sleepLeftMs();
|
||||
if (sleep == -1) st = Util.join(st, "Sleep at end");
|
||||
else if (sleep > 0) st = Util.join(st, "Sleep in " + Util.clock(sleep));
|
||||
if (sleep == -1) st = Util.join(st, "Pausa ao final do episodio");
|
||||
else if (sleep > 0) st = Util.join(st, "Pausa en " + Util.clock(sleep));
|
||||
seek.setSecondaryProgress(ps.bufferedPercent() * 10);
|
||||
setControls(true);
|
||||
} else {
|
||||
@@ -204,7 +204,7 @@ public class PlayerActivity extends BaseActivity {
|
||||
double cur = pr != null ? pr[0] : Math.max(0, App.localPosition(this, e.id));
|
||||
p = (int) (cur * 1000);
|
||||
dur = (int) (e.duration * 1000);
|
||||
st = pending != null ? "Connecting…" : (Downloads.has(this, e.id) ? "Saved on SD card" : "");
|
||||
st = pending != null ? "Conectando…" : (Downloads.has(this, e.id) ? "Gardado na tarxeta SD" : "");
|
||||
playing = pending != null;
|
||||
seek.setSecondaryProgress(0);
|
||||
setControls(true);
|
||||
@@ -234,17 +234,17 @@ public class PlayerActivity extends BaseActivity {
|
||||
if (e == null) return false;
|
||||
PlayerService ps = PlayerService.get();
|
||||
boolean active = ps != null && ps.episode() != null && ps.episode().id.equals(e.id);
|
||||
if (active) menu.add(0, 1, 0, "Sleep timer").setIcon(android.R.drawable.ic_lock_idle_alarm);
|
||||
menu.add(0, 2, 0, "Mark as played").setIcon(android.R.drawable.ic_menu_agenda);
|
||||
if (active) menu.add(0, 1, 0, "Temporizador de pausa").setIcon(android.R.drawable.ic_lock_idle_alarm);
|
||||
menu.add(0, 2, 0, "Marcar como escoitado").setIcon(android.R.drawable.ic_menu_agenda);
|
||||
if (DownloadService.busyWith(e.id)) {
|
||||
menu.add(0, 3, 0, "Cancel download").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
menu.add(0, 3, 0, "Cancelar descarga").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
} else if (Downloads.has(this, e.id)) {
|
||||
menu.add(0, 3, 0, "Delete download").setIcon(android.R.drawable.ic_menu_delete);
|
||||
menu.add(0, 3, 0, "Eliminar descarga").setIcon(android.R.drawable.ic_menu_delete);
|
||||
} else {
|
||||
menu.add(0, 3, 0, "Download").setIcon(android.R.drawable.ic_menu_save);
|
||||
menu.add(0, 3, 0, "Descargar").setIcon(android.R.drawable.ic_menu_save);
|
||||
}
|
||||
menu.add(0, 4, 0, "Go to show").setIcon(android.R.drawable.ic_menu_view);
|
||||
if (active) menu.add(0, 5, 0, "Stop").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
menu.add(0, 4, 0, "Ir ao programa").setIcon(android.R.drawable.ic_menu_view);
|
||||
if (active) menu.add(0, 5, 0, "Deter").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ public class PlayerActivity extends BaseActivity {
|
||||
case 2:
|
||||
if (active) {
|
||||
ps.finishAndStop();
|
||||
toast("Marked as played");
|
||||
toast("Marcado como escoitado");
|
||||
} else {
|
||||
episodeAction(M_PLAYED, e, null);
|
||||
}
|
||||
@@ -285,15 +285,15 @@ public class PlayerActivity extends BaseActivity {
|
||||
|
||||
private void sleepDialog() {
|
||||
final int[] minutes = {0, 5, 10, 15, 30, 45, 60, -1};
|
||||
String[] labels = {"Off", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "45 minutes", "1 hour",
|
||||
"End of episode"};
|
||||
new AlertDialog.Builder(this).setTitle("Sleep timer")
|
||||
String[] labels = {"Desactivado", "5 minutos", "10 minutos", "15 minutos", "30 minutos", "45 minutos", "1 hora",
|
||||
"Ao final do episodio"};
|
||||
new AlertDialog.Builder(this).setTitle("Temporizador de pausa")
|
||||
.setItems(labels, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps == null) return;
|
||||
ps.setSleep(minutes[which]);
|
||||
toast(minutes[which] == 0 ? "Sleep timer off" : "Sleep timer set");
|
||||
toast(minutes[which] == 0 ? "Temporizador desactivado" : "Temporizador activado");
|
||||
refresh();
|
||||
}
|
||||
}).show();
|
||||
|
||||
@@ -256,7 +256,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
}
|
||||
lastPosMs = positionMs();
|
||||
state = PAUSED;
|
||||
status = "Paused";
|
||||
status = "En pausa";
|
||||
pausedAt = SystemClock.elapsedRealtime();
|
||||
syncNow(false);
|
||||
wifi.release();
|
||||
@@ -345,7 +345,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
markFinished();
|
||||
releasePlayer();
|
||||
state = ENDED;
|
||||
status = "Marked as played";
|
||||
status = "Marcado como escoitado";
|
||||
lastPosMs = dur;
|
||||
pausedAt = SystemClock.elapsedRealtime();
|
||||
wifi.release();
|
||||
@@ -364,7 +364,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
private void open(final Episode e, final double startSec, boolean playNow) {
|
||||
releasePlayer();
|
||||
state = LOADING;
|
||||
status = "Connecting…";
|
||||
status = "Conectando…";
|
||||
playOnPrepared = playNow;
|
||||
showNotification(true);
|
||||
final int gen = ++generation;
|
||||
@@ -407,9 +407,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
JSONObject t = tracks == null ? null : tracks.optJSONObject(0);
|
||||
String cu = t == null ? "" : Util.str(t, "contentUrl");
|
||||
if (cu.length() == 0) {
|
||||
s.error = "The server sent no audio for this episode.";
|
||||
s.error = "O servidor non enviou audio para este episodio.";
|
||||
} else if (cu.contains(".m3u8")) {
|
||||
s.error = "This episode needs transcoding, which Android 2.x can't play. Try downloading it.";
|
||||
s.error = "Este episodio precisa transcodificación, que Android 2.x non pode reproducir. Proba a descargalo.";
|
||||
} else {
|
||||
s.contentUrl = cu;
|
||||
s.url = streamUrl(c, cu);
|
||||
@@ -442,7 +442,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
local = s.local;
|
||||
resumeMs = (int) (s.start * 1000);
|
||||
lastPosMs = resumeMs;
|
||||
prepare(s.url, s.offline ? "Offline · progress will sync later" : null);
|
||||
prepare(s.url, s.offline ? "Sen conexión · o progreso sincronizarase despois" : null);
|
||||
}
|
||||
|
||||
private void prepare(String url, String note) {
|
||||
@@ -467,10 +467,10 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
wifi.acquire();
|
||||
}
|
||||
state = LOADING;
|
||||
status = note != null ? note : local ? "Opening saved file…" : "Buffering…";
|
||||
status = note != null ? note : local ? "Abrindo o ficheiro gardado…" : "Cargando…";
|
||||
mp.prepareAsync();
|
||||
} catch (Exception ex) {
|
||||
fail("Can't open the audio: " + Util.describe(ex));
|
||||
fail("Non se pode abrir o audio: " + Util.describe(ex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
start();
|
||||
} else {
|
||||
state = PAUSED;
|
||||
status = "Paused";
|
||||
status = "En pausa";
|
||||
showNotification(false);
|
||||
}
|
||||
}
|
||||
@@ -506,12 +506,12 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
mp.setVolume(1f, 1f);
|
||||
mp.start();
|
||||
} catch (Exception ex) {
|
||||
fail("Playback failed: " + Util.describe(ex));
|
||||
fail("Fallou a reprodución: " + Util.describe(ex));
|
||||
return;
|
||||
}
|
||||
if (!local) wifi.acquire();
|
||||
state = PLAYING;
|
||||
status = local ? (sessionId == null ? "Saved file · offline" : "Saved file") : "Streaming";
|
||||
status = local ? (sessionId == null ? "Ficheiro gardado · sen conexión" : "Ficheiro gardado") : "Reproducindo en liña";
|
||||
playingSince = SystemClock.elapsedRealtime();
|
||||
lastTick = playingSince;
|
||||
if (lastSync == 0) lastSync = playingSince;
|
||||
@@ -555,11 +555,11 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
// Old Android's streamer "completes" when the connection drops. Treat that as an error.
|
||||
if (!local && dur > 0 && pos < dur - 30000) {
|
||||
lastPosMs = pos;
|
||||
retryStream("Connection lost, reconnecting…");
|
||||
retryStream("Perdeuse a conexión, reconectando…");
|
||||
return;
|
||||
}
|
||||
state = ENDED;
|
||||
status = "Finished";
|
||||
status = "Rematado";
|
||||
lastPosMs = dur;
|
||||
pausedAt = SystemClock.elapsedRealtime();
|
||||
wifi.release();
|
||||
@@ -573,9 +573,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
if (p != mp) return true;
|
||||
if (prepared) lastPosMs = positionMs();
|
||||
if (!local && contentUrl != null) {
|
||||
retryStream("Connection lost, reconnecting…");
|
||||
retryStream("Perdeuse a conexión, reconectando…");
|
||||
} else {
|
||||
fail("Playback error (" + what + "/" + extra + ").");
|
||||
fail("Erro de reprodución (" + what + "/" + extra + ").");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -583,7 +583,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
/** Re-opens the stream at the last position with a fresh token. Gives up after three tries. */
|
||||
private void retryStream(final String msg) {
|
||||
if (retries >= 3 || contentUrl == null) {
|
||||
fail("Lost connection to the server. Press play to try again.");
|
||||
fail("Perdeuse a conexión co servidor. Preme reproducir para tentalo de novo.");
|
||||
retries = 0;
|
||||
return;
|
||||
}
|
||||
@@ -675,7 +675,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
if (sleepAt > 0 && now >= sleepAt) {
|
||||
sleepAt = 0;
|
||||
pause();
|
||||
status = "Sleep timer: paused";
|
||||
status = "Temporizador: en pausa";
|
||||
}
|
||||
} else if (state == PAUSED || state == ENDED || state == ERROR) {
|
||||
// Free the phone's memory if left paused for a long time.
|
||||
@@ -756,11 +756,11 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
String what;
|
||||
switch (state) {
|
||||
case PLAYING: what = "Playing"; break;
|
||||
case LOADING: what = "Loading…"; break;
|
||||
case ENDED: what = "Finished"; break;
|
||||
case ERROR: what = "Stopped: tap to retry"; break;
|
||||
default: what = "Paused"; break;
|
||||
case PLAYING: what = "Reproducindo"; break;
|
||||
case LOADING: what = "Cargando…"; break;
|
||||
case ENDED: what = "Rematado"; break;
|
||||
case ERROR: what = "Detido: toca para tentalo de novo"; break;
|
||||
default: what = "En pausa"; break;
|
||||
}
|
||||
n.setLatestEventInfo(this, ep.title, Util.join(what, ep.show), pi);
|
||||
if (foreground) {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class PodcastActivity extends BaseActivity {
|
||||
title.setText(getIntent().getStringExtra("title"));
|
||||
subtitle.setText(getIntent().getStringExtra("author"));
|
||||
Covers.load(this, (ImageView) findViewById(R.id.cover), itemId,
|
||||
(int) (44 * getResources().getDisplayMetrics().density));
|
||||
getResources().getDimensionPixelSize(R.dimen.cover_podcast));
|
||||
|
||||
Prefs p = Prefs.get(this);
|
||||
oldestFirst = p.sp.getBoolean("oldest_first", false);
|
||||
@@ -104,7 +104,7 @@ public class PodcastActivity extends BaseActivity {
|
||||
loading.setVisibility(View.GONE);
|
||||
episodes = list;
|
||||
if (showTitle.length() > 0) title.setText(showTitle);
|
||||
String n = list.size() == 1 ? "1 episode" : list.size() + " episodes";
|
||||
String n = list.size() == 1 ? "1 episodio" : list.size() + " episodios";
|
||||
subtitle.setText(Util.join(author, n));
|
||||
render();
|
||||
}
|
||||
@@ -145,10 +145,10 @@ public class PodcastActivity extends BaseActivity {
|
||||
}
|
||||
rows.add(Rows.Row.episode(e));
|
||||
}
|
||||
if (hidden > 0) rows.add(0, Rows.Row.header(hidden + " PLAYED EPISODES HIDDEN"));
|
||||
if (hidden > 0) rows.add(0, Rows.Row.header(hidden + " EPISODIOS ESCOITADOS OCULTOS"));
|
||||
adapter.set(rows);
|
||||
boolean none = rows.isEmpty();
|
||||
empty.setText(none ? "No episodes." : "");
|
||||
empty.setText(none ? "Non hai episodios." : "");
|
||||
empty.setVisibility(none ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@@ -172,10 +172,10 @@ public class PodcastActivity extends BaseActivity {
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.clear();
|
||||
menu.add(0, 1, 0, oldestFirst ? "Newest first" : "Oldest first").setIcon(android.R.drawable.ic_menu_sort_by_size);
|
||||
menu.add(0, 2, 0, hidePlayed ? "Show played" : "Hide played").setIcon(android.R.drawable.ic_menu_view);
|
||||
menu.add(0, 3, 0, "Refresh").setIcon(android.R.drawable.ic_menu_rotate);
|
||||
menu.add(0, 4, 0, "Now playing").setIcon(android.R.drawable.ic_media_play);
|
||||
menu.add(0, 1, 0, oldestFirst ? "Máis recentes primeiro" : "Máis antigos primeiro").setIcon(android.R.drawable.ic_menu_sort_by_size);
|
||||
menu.add(0, 2, 0, hidePlayed ? "Amosar escoitados" : "Ocultar escoitados").setIcon(android.R.drawable.ic_menu_view);
|
||||
menu.add(0, 3, 0, "Actualizar").setIcon(android.R.drawable.ic_menu_rotate);
|
||||
menu.add(0, 4, 0, "En reprodución").setIcon(android.R.drawable.ic_media_play);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ final class Rows extends BaseAdapter {
|
||||
Rows(Activity a) {
|
||||
act = a;
|
||||
inf = LayoutInflater.from(a);
|
||||
coverPx = (int) (40 * a.getResources().getDisplayMetrics().density);
|
||||
coverPx = a.getResources().getDimensionPixelSize(R.dimen.cover_row);
|
||||
}
|
||||
|
||||
void set(List<Row> list) {
|
||||
@@ -147,7 +147,7 @@ final class Rows extends BaseAdapter {
|
||||
|
||||
private void bindShow(View v, Show s) {
|
||||
((TextView) v.findViewById(R.id.title)).setText(s.title);
|
||||
String n = s.episodes == 1 ? "1 episode" : s.episodes + " episodes";
|
||||
String n = s.episodes == 1 ? "1 episodio" : s.episodes + " episodios";
|
||||
((TextView) v.findViewById(R.id.meta)).setText(Util.join(s.author, n));
|
||||
Covers.load(act, (ImageView) v.findViewById(R.id.cover), s.id, coverPx);
|
||||
}
|
||||
@@ -175,21 +175,21 @@ final class Rows extends BaseAdapter {
|
||||
|
||||
String m = Util.date(e.published);
|
||||
if (finished) {
|
||||
m = Util.join(m, "Played");
|
||||
m = Util.join(m, "Escoitado");
|
||||
} else if (started && dur > 0) {
|
||||
m = Util.join(m, Util.duration(Math.max(0, dur - cur)) + " left");
|
||||
m = Util.join(m, "quedan " + Util.duration(Math.max(0, dur - cur)));
|
||||
} else if (dur > 0) {
|
||||
m = Util.join(m, Util.duration(dur));
|
||||
}
|
||||
if (DownloadService.busyWith(e.id)) {
|
||||
Episode now = DownloadService.current;
|
||||
m = Util.join(m, now != null && now.id.equals(e.id) ? "Saving " + DownloadService.percent + "%" : "Queued");
|
||||
m = Util.join(m, now != null && now.id.equals(e.id) ? "Gardando " + DownloadService.percent + "%" : "Na cola");
|
||||
} else if (Downloads.has(act, e.id)) {
|
||||
m = Util.join(m, "Saved");
|
||||
m = Util.join(m, "Gardado");
|
||||
}
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps != null && ps.episode() != null && ps.episode().id.equals(e.id) && ps.isPlaying()) {
|
||||
m = Util.join("Now playing", m);
|
||||
m = Util.join("En reprodución", m);
|
||||
}
|
||||
meta.setText(m);
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
|
||||
Preference.OnPreferenceChangeListener skipSummary = new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference pref, Object value) {
|
||||
pref.setSummary(value + " seconds");
|
||||
pref.setSummary(value + " segundos");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
ListPreference back = (ListPreference) findPreference("skip_back");
|
||||
ListPreference fwd = (ListPreference) findPreference("skip_fwd");
|
||||
back.setSummary(p.skipBack() + " seconds");
|
||||
fwd.setSummary(p.skipFwd() + " seconds");
|
||||
back.setSummary(p.skipBack() + " segundos");
|
||||
fwd.setSummary(p.skipFwd() + " segundos");
|
||||
back.setOnPreferenceChangeListener(skipSummary);
|
||||
fwd.setOnPreferenceChangeListener(skipSummary);
|
||||
|
||||
@@ -35,42 +35,42 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
}
|
||||
});
|
||||
|
||||
String who = p.username().length() > 0 ? p.username() : "API key";
|
||||
String who = p.username().length() > 0 ? p.username() : "Chave da API";
|
||||
findPreference("account").setSummary(who + " @ " + p.server());
|
||||
|
||||
findPreference("delete_all").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference pref) {
|
||||
new AlertDialog.Builder(SettingsActivity.this)
|
||||
.setMessage("Delete every downloaded episode from the SD card?")
|
||||
.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Eliminar todos os episodios descargados da tarxeta SD?")
|
||||
.setPositiveButton("Eliminar", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps != null && ps.isLocal()) ps.stop();
|
||||
int n = Downloads.deleteAll(SettingsActivity.this);
|
||||
Toast.makeText(SettingsActivity.this, n + " deleted", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(SettingsActivity.this, n + " eliminados", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null).show();
|
||||
.setNegativeButton("Cancelar", null).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference pins = findPreference("pins");
|
||||
int n = Trust.pinned(this).size();
|
||||
pins.setSummary(n == 0 ? "None" : n + " (tap to forget)");
|
||||
pins.setSummary(n == 0 ? "Ningún" : n + " (toca para esquecelos)");
|
||||
pins.setEnabled(n > 0);
|
||||
pins.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference pref) {
|
||||
new AlertDialog.Builder(SettingsActivity.this)
|
||||
.setMessage("Forget the certificates you chose to trust? You'll be asked again next time.")
|
||||
.setPositiveButton("Forget", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Esquecer os certificados nos que decidiches confiar? Preguntarase de novo a próxima vez.")
|
||||
.setPositiveButton("Esquecer", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
Trust.forgetPins(SettingsActivity.this);
|
||||
pins.setSummary("None");
|
||||
pins.setSummary("Ningún");
|
||||
pins.setEnabled(false);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null).show();
|
||||
.setNegativeButton("Cancelar", null).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -78,8 +78,8 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
findPreference("logout").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference pref) {
|
||||
new AlertDialog.Builder(SettingsActivity.this)
|
||||
.setMessage("Log out of " + p.server() + "? Downloads stay on the SD card.")
|
||||
.setPositiveButton("Log out", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Pechar sesión en " + p.server() + "? As descargas quedan na tarxeta SD.")
|
||||
.setPositiveButton("Pechar sesión", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps != null) ps.stop();
|
||||
@@ -89,7 +89,7 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
finish(); // Home sees the logout on resume and opens the login screen
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null).show();
|
||||
.setNegativeButton("Cancelar", null).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.security.MessageDigest;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -67,7 +66,7 @@ final class Trust {
|
||||
ctx.init(null, new TrustManager[]{new Manager(c.getApplicationContext())}, null);
|
||||
factory = ctx.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Secure connections aren't available: " + e.getMessage());
|
||||
throw new IOException("As conexións seguras non están dispoñibles: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return factory;
|
||||
@@ -326,15 +325,15 @@ final class Trust {
|
||||
X509Certificate leaf = r.chain[0];
|
||||
X509Certificate top = top(r.chain);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("This phone doesn't recognise the certificate of ").append(host).append(".\n\n");
|
||||
sb.append("Issued to: ").append(commonName(leaf.getSubjectX500Principal())).append('\n');
|
||||
sb.append("Issued by: ").append(commonName(leaf.getIssuerX500Principal())).append('\n');
|
||||
sb.append("Valid until: ").append(DateFormat.getDateInstance().format(leaf.getNotAfter())).append("\n\n");
|
||||
sb.append("SHA-256 fingerprint:\n").append(fingerprint(leaf)).append("\n\n");
|
||||
sb.append("Only continue if this matches the fingerprint your computer's browser shows for the site.");
|
||||
sb.append("Este teléfono non recoñece o certificado de ").append(host).append(".\n\n");
|
||||
sb.append("Emitido para: ").append(commonName(leaf.getSubjectX500Principal())).append('\n');
|
||||
sb.append("Emitido por: ").append(commonName(leaf.getIssuerX500Principal())).append('\n');
|
||||
sb.append("Válido ata: ").append(Util.date(leaf.getNotAfter().getTime(), true)).append("\n\n");
|
||||
sb.append("Pegada dixital SHA-256:\n").append(fingerprint(leaf)).append("\n\n");
|
||||
sb.append("Continúa só se coincide coa pegada que amosa o navegador do teu ordenador para este sitio.");
|
||||
if (top != leaf) {
|
||||
sb.append("\n\nMiniABS will then trust certificates issued by \"")
|
||||
.append(commonName(top.getSubjectX500Principal())).append("\".");
|
||||
sb.append("\n\nMiniABS confiará desde agora nos certificados emitidos por «")
|
||||
.append(commonName(top.getSubjectX500Principal())).append("».");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -24,19 +24,19 @@ final class TrustPrompt {
|
||||
try {
|
||||
host = new URL(Prefs.get(a).server()).getHost();
|
||||
} catch (Exception e) {
|
||||
host = "the server";
|
||||
host = "o servidor";
|
||||
}
|
||||
showing = true;
|
||||
new AlertDialog.Builder(a)
|
||||
.setTitle("Check this certificate")
|
||||
.setTitle("Comproba este certificado")
|
||||
.setMessage(Trust.describe(r, host))
|
||||
.setPositiveButton("Trust", new DialogInterface.OnClickListener() {
|
||||
.setPositiveButton("Confiar", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
Trust.pin(a, r.chain);
|
||||
if (retry != null) retry.run();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setNegativeButton("Cancelar", null)
|
||||
.setOnCancelListener(null)
|
||||
.show()
|
||||
.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
|
||||
@@ -5,10 +5,7 @@ import java.net.ConnectException;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
@@ -47,12 +44,24 @@ final class Util {
|
||||
return m + ":" + ss;
|
||||
}
|
||||
|
||||
/** Galician month abbreviations (Android 2.3 has no Galician date data of its own). */
|
||||
private static final String[] MONTHS = {"xan", "feb", "mar", "abr", "mai", "xuñ", "xul", "ago", "set", "out",
|
||||
"nov", "dec"};
|
||||
|
||||
/** "14 out", or "14 out 2025" for other years. */
|
||||
static String date(long ms) {
|
||||
if (ms <= 0) return "";
|
||||
Calendar now = Calendar.getInstance(), c = Calendar.getInstance();
|
||||
c.setTimeInMillis(ms);
|
||||
String pattern = c.get(Calendar.YEAR) == now.get(Calendar.YEAR) ? "d MMM" : "d MMM yyyy";
|
||||
return new SimpleDateFormat(pattern, Locale.getDefault()).format(new Date(ms));
|
||||
return date(ms, c.get(Calendar.YEAR) != now.get(Calendar.YEAR));
|
||||
}
|
||||
|
||||
static String date(long ms, boolean withYear) {
|
||||
if (ms <= 0) return "";
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(ms);
|
||||
String d = c.get(Calendar.DAY_OF_MONTH) + " " + MONTHS[c.get(Calendar.MONTH)];
|
||||
return withYear ? d + " " + c.get(Calendar.YEAR) : d;
|
||||
}
|
||||
|
||||
static String plainText(String html) {
|
||||
@@ -87,28 +96,28 @@ final class Util {
|
||||
if (e instanceof Api.AuthException) return e.getMessage();
|
||||
if (e instanceof Api.ApiException) {
|
||||
Api.ApiException a = (Api.ApiException) e;
|
||||
if (a.code == 401) return "Not signed in. Please log in again.";
|
||||
if (a.code == 403) return "Your account isn't allowed to do that.";
|
||||
if (a.code == 404) return "Not found on the server.";
|
||||
if (a.code == 401) return "Non iniciaches sesión. Inicia sesión de novo.";
|
||||
if (a.code == 403) return "A túa conta non ten permiso para facer iso.";
|
||||
if (a.code == 404) return "Non se atopou no servidor.";
|
||||
String m = a.getMessage();
|
||||
return "Server error " + a.code + (m != null && m.length() > 0 ? ": " + m : "");
|
||||
return "Erro do servidor " + a.code + (m != null && m.length() > 0 ? ": " + m : "");
|
||||
}
|
||||
if (e instanceof UnknownHostException) return "Server not found. Check the address and your connection.";
|
||||
if (e instanceof SocketTimeoutException) return "The server took too long to answer.";
|
||||
if (e instanceof ConnectException) return "Can't connect to the server. Is it running and reachable?";
|
||||
if (e instanceof UnknownHostException) return "Non se atopou o servidor. Comproba o enderezo e a conexión.";
|
||||
if (e instanceof SocketTimeoutException) return "O servidor tardou demasiado en responder.";
|
||||
if (e instanceof ConnectException) return "Non se pode conectar co servidor. Está en marcha e accesible?";
|
||||
if (e instanceof SSLException) {
|
||||
Trust.Rejected r = Trust.recentRejection();
|
||||
if (r != null) {
|
||||
return r.expired() ? "The server's certificate has expired, or the phone's date is wrong."
|
||||
: "This phone doesn't recognise the server's certificate.";
|
||||
return r.expired() ? "O certificado do servidor caducou ou a data do teléfono é incorrecta."
|
||||
: "Este teléfono non recoñece o certificado do servidor.";
|
||||
}
|
||||
return "Couldn't set up a secure connection. Android 2.3 only speaks TLS 1.0, so the server needs "
|
||||
+ "a legacy HTTPS port (see README). On your home network, the http:// address works too.";
|
||||
return "Non se puido establecer unha conexión segura. Android 2.3 só admite TLS 1.0, así que o servidor "
|
||||
+ "precisa un porto HTTPS antigo (ve o README). Na rede da casa tamén funciona o enderezo http://.";
|
||||
}
|
||||
if (e instanceof SocketException) return "Connection dropped. Check your network.";
|
||||
if (e instanceof FileNotFoundException) return "Not found on the server.";
|
||||
if (e instanceof SocketException) return "Cortouse a conexión. Comproba a rede.";
|
||||
if (e instanceof FileNotFoundException) return "Non se atopou no servidor.";
|
||||
String m = e.getMessage();
|
||||
if (m != null && m.contains("not verified")) return "The server's certificate is for a different address.";
|
||||
if (m != null && m.contains("not verified")) return "O certificado do servidor é doutro enderezo.";
|
||||
return m == null || m.length() == 0 ? e.getClass().getSimpleName() : m;
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class ApiTest {
|
||||
Api.login(c, p.server(), "demo", "wrong");
|
||||
check(false, "wrong password rejected");
|
||||
} catch (Api.ApiException e) {
|
||||
check(e.code == 401 && e.getMessage().contains("Wrong"), "wrong password rejected with a clear message");
|
||||
check(e.code == 401 && e.getMessage().contains("contrasinal incorrectos"), "wrong password rejected with a clear message");
|
||||
}
|
||||
String lib = Api.login(c, p.server(), "demo", "pw");
|
||||
check(lib.equals("lib-pod"), "login returns default library");
|
||||
|
||||
@@ -88,13 +88,13 @@ public class TrustTest {
|
||||
Exception e = tryStatus(good);
|
||||
Trust.Rejected r = Trust.recentRejection();
|
||||
check(e instanceof javax.net.ssl.SSLException && r != null, "unrecognised certificate rejected and remembered");
|
||||
check(Util.describe(e).equals("This phone doesn't recognise the server's certificate."),
|
||||
check(Util.describe(e).equals("Este teléfono non recoñece o certificado do servidor."),
|
||||
"error message: \"" + Util.describe(e) + "\"");
|
||||
String text = Trust.describe(r, "localhost");
|
||||
byte[] d = MessageDigest.getInstance("SHA-256").digest(leaf.getEncoded());
|
||||
String fp = String.format("%02X:%02X:%02X", d[0], d[1], d[2]);
|
||||
check(text.contains(fp) && text.contains("Issued by: Test Intermediate R1")
|
||||
&& text.contains("trust certificates issued by \"Test Intermediate R1\""),
|
||||
check(text.contains(fp) && text.contains("Emitido por: Test Intermediate R1")
|
||||
&& text.contains("certificados emitidos por «Test Intermediate R1»"),
|
||||
"prompt shows the server's fingerprint and what will be trusted");
|
||||
X509Certificate pinned = Trust.pin(c, r.chain);
|
||||
check(pinned.equals(inter), "trusting pins the issuing CA, so renewals keep working");
|
||||
@@ -145,7 +145,7 @@ public class TrustTest {
|
||||
check(tryStatus(forged) != null, "...and still rejected even if the user taps Trust");
|
||||
Exception ee = tryStatus(expired);
|
||||
check(ee != null && Trust.recentRejection() != null && Trust.recentRejection().expired()
|
||||
&& Util.describe(ee).contains("expired"),
|
||||
&& Util.describe(ee).contains("caducou"),
|
||||
"expired certificate rejected: \"" + Util.describe(ee) + "\"");
|
||||
|
||||
System.out.println("Self-signed server");
|
||||
|
||||
@@ -88,9 +88,9 @@ def refresh(px, folder):
|
||||
for folder, k in DENS.items():
|
||||
launcher(round(48 * k), folder)
|
||||
status(round(25 * k), folder)
|
||||
play(round(40 * k), folder, "ic_play", DARK)
|
||||
pause(round(40 * k), folder, "ic_pause", DARK)
|
||||
play(round(28 * k), folder, "ic_play_small", AMBER)
|
||||
pause(round(28 * k), folder, "ic_pause_small", AMBER)
|
||||
refresh(round(24 * k), folder)
|
||||
play(round(48 * k), folder, "ic_play", DARK)
|
||||
pause(round(48 * k), folder, "ic_pause", DARK)
|
||||
play(round(38 * k), folder, "ic_play_small", AMBER)
|
||||
pause(round(38 * k), folder, "ic_pause_small", AMBER)
|
||||
refresh(round(30 * k), folder)
|
||||
print("icons ok")
|
||||
@@ -0,0 +1,260 @@
|
||||
"""One-off: translates MiniABS's interface strings to Galician in place.
|
||||
Each (file, old, new) must match at least once, so nothing is silently missed."""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
ROOT = os.path.join(os.path.dirname(__file__), "..")
|
||||
S = "src/com/miniabs/"
|
||||
|
||||
R = [
|
||||
# ---------------- layouts / xml
|
||||
("res/layout/activity_login.xml", 'android:text="Audiobookshelf podcasts"', 'android:text="Podcasts de Audiobookshelf"'),
|
||||
("res/layout/activity_login.xml", 'android:text="Server address"', 'android:text="Enderezo do servidor"'),
|
||||
("res/layout/activity_login.xml", 'android:text="Username"', 'android:text="Usuario"'),
|
||||
("res/layout/activity_login.xml", 'android:text="Password"', 'android:text="Contrasinal"'),
|
||||
("res/layout/activity_login.xml", 'android:text="Use an API key instead"', 'android:text="Usar unha chave da API"'),
|
||||
("res/layout/activity_login.xml", 'android:text="Connect"', 'android:text="Conectar"'),
|
||||
("res/layout/activity_login.xml",
|
||||
"android:text=\"At home, use your server's local http:// address. For https://, Android 2.3 needs the server to offer TLS 1.0 on a separate port (see README). If the certificate isn't recognised, you'll be shown its fingerprint to check.\"",
|
||||
'android:text="Na casa, usa o enderezo local http:// do servidor. Para https://, Android 2.3 precisa que o servidor ofreza TLS 1.0 nun porto á parte (ve o README). Se o certificado non se recoñece, amosaráseche a súa pegada dixital para que a comprobes."'),
|
||||
("res/layout/activity_home.xml", 'android:text="Latest"', 'android:text="Últimos"'),
|
||||
("res/layout/activity_home.xml", 'android:text="Shows"', 'android:text="Programas"'),
|
||||
("res/layout/activity_home.xml", 'android:text="Saved"', 'android:text="Gardados"'),
|
||||
("res/xml/settings.xml", 'android:title="Playback"', 'android:title="Reprodución"'),
|
||||
("res/xml/settings.xml", 'android:title="Skip back"', 'android:title="Retroceder"'),
|
||||
("res/xml/settings.xml", 'android:title="Skip forward"', 'android:title="Avanzar"'),
|
||||
("res/xml/settings.xml", 'android:title="Downloads"', 'android:title="Descargas"'),
|
||||
("res/xml/settings.xml", 'android:title="Download on Wi-Fi only"', 'android:title="Descargar só con wifi"'),
|
||||
("res/xml/settings.xml", 'android:summary="Saves your mobile data"', 'android:summary="Aforra datos móbiles"'),
|
||||
("res/xml/settings.xml", 'android:title="Delete all downloads"', 'android:title="Eliminar todas as descargas"'),
|
||||
("res/xml/settings.xml", 'android:title="Display"', 'android:title="Aparencia"'),
|
||||
("res/xml/settings.xml", 'android:title="Show cover art"', 'android:title="Amosar portadas"'),
|
||||
("res/xml/settings.xml", 'android:summary="Turn off to save data and memory"', 'android:summary="Desactívao para aforrar datos e memoria"'),
|
||||
("res/xml/settings.xml", 'android:title="Account"', 'android:title="Conta"'),
|
||||
("res/xml/settings.xml", 'android:title="Signed in"', 'android:title="Sesión iniciada"'),
|
||||
("res/xml/settings.xml", 'android:title="Certificates you trusted"', 'android:title="Certificados nos que confías"'),
|
||||
("res/xml/settings.xml", 'android:title="Log out"', 'android:title="Pechar sesión"'),
|
||||
("res/xml/settings.xml", 'android:title="About"', 'android:title="Acerca de"'),
|
||||
("res/xml/settings.xml", 'android:summary="A tiny Audiobookshelf podcast client for Android 2.2+"',
|
||||
'android:summary="Un pequeno cliente de podcasts de Audiobookshelf para Android 2.2+"'),
|
||||
("res/values/arrays.xml", " seconds</item>", " segundos</item>"),
|
||||
("AndroidManifest.xml", 'android:label="Settings"', 'android:label="Axustes"'),
|
||||
|
||||
# ---------------- Api / Json / Trust (errors)
|
||||
(S + "Api.java", '"Unexpected reply from server"', '"Resposta inesperada do servidor"'),
|
||||
(S + "Json.java", '"Unexpected reply from server"', '"Resposta inesperada do servidor"'),
|
||||
(S + "Json.java", '"Reply was cut off"', '"A resposta chegou incompleta"'),
|
||||
(S + "Api.java", '"Your sign-in has expired. Please log in again."', '"A túa sesión caducou. Inicia sesión de novo."'),
|
||||
(S + "Api.java", '"The server redirected; check the address in Settings."', '"O servidor redirixiu a petición; comproba o enderezo do servidor."'),
|
||||
(S + "Api.java", '"Please log in."', '"Inicia sesión."'),
|
||||
(S + "Api.java", '"No Audiobookshelf server at this address."', '"Non hai ningún servidor de Audiobookshelf neste enderezo."'),
|
||||
(S + "Api.java", "\"This server hasn't been set up yet. Finish setup in a browser first.\"",
|
||||
'"Este servidor aínda non está configurado. Remata a configuración nun navegador."'),
|
||||
(S + "Api.java", '"Wrong username or password."', '"Usuario ou contrasinal incorrectos."'),
|
||||
(S + "Api.java", '"Too many attempts. Wait a minute and try again."', '"Demasiados intentos. Agarda un minuto e téntao de novo."'),
|
||||
(S + "Api.java", "\"The server didn't return a sign-in token.\"", '"O servidor non devolveu ningún token de sesión."'),
|
||||
(S + "Api.java", "\"That API key wasn't accepted.\"", '"Non se aceptou esa chave da API."'),
|
||||
(S + "Api.java", "\"Certificate doesn't match \"", '"O certificado non corresponde a "'),
|
||||
(S + "Trust.java", "\"Secure connections aren't available: \"", '"As conexións seguras non están dispoñibles: "'),
|
||||
(S + "Trust.java", '"This phone doesn\'t recognise the certificate of "', '"Este teléfono non recoñece o certificado de "'),
|
||||
(S + "Trust.java", '"Issued to: "', '"Emitido para: "'),
|
||||
(S + "Trust.java", '"Issued by: "', '"Emitido por: "'),
|
||||
(S + "Trust.java", '"Valid until: "', '"Válido ata: "'),
|
||||
(S + "Trust.java", '"SHA-256 fingerprint:\\n"', '"Pegada dixital SHA-256:\\n"'),
|
||||
(S + "Trust.java", "\"Only continue if this matches the fingerprint your computer's browser shows for the site.\"",
|
||||
'"Continúa só se coincide coa pegada que amosa o navegador do teu ordenador para este sitio."'),
|
||||
(S + "Trust.java", '"\\n\\nMiniABS will then trust certificates issued by \\""', '"\\n\\nMiniABS confiará desde agora nos certificados emitidos por «"'),
|
||||
(S + "Trust.java", '.append("\\".");', '.append("».");'),
|
||||
(S + "Trust.java", "DateFormat.getDateInstance().format(leaf.getNotAfter())", "Util.date(leaf.getNotAfter().getTime(), true)"),
|
||||
(S + "Trust.java", "import java.text.DateFormat;\n", ""),
|
||||
(S + "TrustPrompt.java", '"the server"', '"o servidor"'),
|
||||
(S + "TrustPrompt.java", '"Check this certificate"', '"Comproba este certificado"'),
|
||||
(S + "TrustPrompt.java", '"Trust"', '"Confiar"'),
|
||||
(S + "TrustPrompt.java", '"Cancel"', '"Cancelar"'),
|
||||
|
||||
# ---------------- Util
|
||||
(S + "Util.java", '"Not signed in. Please log in again."', '"Non iniciaches sesión. Inicia sesión de novo."'),
|
||||
(S + "Util.java", "\"Your account isn't allowed to do that.\"", '"A túa conta non ten permiso para facer iso."'),
|
||||
(S + "Util.java", '"Not found on the server."', '"Non se atopou no servidor."'),
|
||||
(S + "Util.java", '"Server error "', '"Erro do servidor "'),
|
||||
(S + "Util.java", '"Server not found. Check the address and your connection."', '"Non se atopou o servidor. Comproba o enderezo e a conexión."'),
|
||||
(S + "Util.java", '"The server took too long to answer."', '"O servidor tardou demasiado en responder."'),
|
||||
(S + "Util.java", "\"Can't connect to the server. Is it running and reachable?\"",
|
||||
'"Non se pode conectar co servidor. Está en marcha e accesible?"'),
|
||||
(S + "Util.java", "\"The server's certificate has expired, or the phone's date is wrong.\"",
|
||||
'"O certificado do servidor caducou ou a data do teléfono é incorrecta."'),
|
||||
(S + "Util.java", "\"This phone doesn't recognise the server's certificate.\"", '"Este teléfono non recoñece o certificado do servidor."'),
|
||||
(S + "Util.java", "\"Couldn't set up a secure connection. Android 2.3 only speaks TLS 1.0, so the server needs \"\n + \"a legacy HTTPS port (see README). On your home network, the http:// address works too.\"",
|
||||
'"Non se puido establecer unha conexión segura. Android 2.3 só admite TLS 1.0, así que o servidor "\n + "precisa un porto HTTPS antigo (ve o README). Na rede da casa tamén funciona o enderezo http://."'),
|
||||
(S + "Util.java", '"Connection dropped. Check your network."', '"Cortouse a conexión. Comproba a rede."'),
|
||||
(S + "Util.java", "\"The server's certificate is for a different address.\"", '"O certificado do servidor é doutro enderezo."'),
|
||||
|
||||
# ---------------- Downloads / DownloadService
|
||||
(S + "Downloads.java", '"No SD card available. Insert or unmount it from USB to download."',
|
||||
'"Non hai tarxeta SD dispoñible. Insírea ou desconéctaa do USB para descargar."'),
|
||||
(S + "Downloads.java", "\"Can't use the SD card.\"", '"Non se pode usar a tarxeta SD."'),
|
||||
(S + "Downloads.java", "\"Can't create a folder on the SD card.\"", '"Non se pode crear un cartafol na tarxeta SD."'),
|
||||
(S + "DownloadService.java", '"Download failed: "', '"Fallou a descarga: "'),
|
||||
(S + "DownloadService.java", '"not on Wi-Fi (Settings: Download on Wi-Fi only)"', '"non hai wifi (Axustes: Descargar só con wifi)"'),
|
||||
(S + "DownloadService.java", '"this episode has no audio file"', '"este episodio non ten ficheiro de audio"'),
|
||||
(S + "DownloadService.java", '"sign-in expired; log in again"', '"a sesión caducou; inicia sesión de novo"'),
|
||||
(S + "DownloadService.java", "\"your account isn't allowed to download\"", '"a túa conta non ten permiso para descargar"'),
|
||||
(S + "DownloadService.java", "\"couldn't save the file\"", '"non se puido gardar o ficheiro"'),
|
||||
(S + "DownloadService.java", '"Downloading " + pct + "%"', '"Descargando " + pct + " %"'),
|
||||
(S + "DownloadService.java", '" more queued"', '" máis na cola"'),
|
||||
|
||||
# ---------------- BaseActivity
|
||||
(S + "BaseActivity.java", '"Certificate trusted. Try again."', '"Certificado aceptado. Téntao de novo."'),
|
||||
(S + "BaseActivity.java", 'Util.clock(dur - pos) + " left"', '"quedan " + Util.clock(dur - pos)'),
|
||||
(S + "BaseActivity.java", '"Paused"', '"En pausa"'),
|
||||
(S + "BaseActivity.java", '"Play"', '"Reproducir"'),
|
||||
(S + "BaseActivity.java", '"Cancel download"', '"Cancelar descarga"'),
|
||||
(S + "BaseActivity.java", '"Delete download"', '"Eliminar descarga"'),
|
||||
(S + "BaseActivity.java", '"Download"', '"Descargar"'),
|
||||
(S + "BaseActivity.java", '"Mark as unplayed"', '"Marcar como non escoitado"'),
|
||||
(S + "BaseActivity.java", '"Mark as played"', '"Marcar como escoitado"'),
|
||||
(S + "BaseActivity.java", '"Go to show"', '"Ir ao programa"'),
|
||||
(S + "BaseActivity.java", "\"You're not on Wi-Fi. Download over mobile data this time?\"",
|
||||
'"Non estás nunha wifi. Descargar con datos móbiles esta vez?"'),
|
||||
(S + "BaseActivity.java", '"Downloading. Wi-Fi-only is now off (change it in Settings)."',
|
||||
'"Descargando. Desactivouse «só con wifi» (podes cambialo en Axustes)."'),
|
||||
(S + "BaseActivity.java", '"Cancel"', '"Cancelar"'),
|
||||
(S + "BaseActivity.java", '"Downloading…"', '"Descargando…"'),
|
||||
(S + "BaseActivity.java", '"Download cancelled"', '"Descarga cancelada"'),
|
||||
(S + "BaseActivity.java", '"Download deleted"', '"Descarga eliminada"'),
|
||||
(S + "BaseActivity.java", '"Marked as played"', '"Marcado como escoitado"'),
|
||||
(S + "BaseActivity.java", '"Marked as unplayed"', '"Marcado como non escoitado"'),
|
||||
|
||||
# ---------------- HomeActivity
|
||||
(S + "HomeActivity.java", '"Synced offline progress"', '"Sincronizouse o progreso gardado sen conexión"'),
|
||||
(S + "HomeActivity.java", '"This server has no podcast libraries."', '"Este servidor non ten bibliotecas de podcasts."'),
|
||||
(S + "HomeActivity.java", '"CONTINUE LISTENING"', '"CONTINUAR ESCOITANDO"'),
|
||||
(S + "HomeActivity.java", '"LATEST EPISODES"', '"ÚLTIMOS EPISODIOS"'),
|
||||
(S + "HomeActivity.java", '"Load more"', '"Cargar máis"'),
|
||||
(S + "HomeActivity.java", '"No episodes yet."', '"Aínda non hai episodios."'),
|
||||
(S + "HomeActivity.java", '"\\n\\nTap refresh to try again."', '"\\n\\nToca actualizar para tentalo de novo."'),
|
||||
(S + "HomeActivity.java", '"No podcasts in this library."', '"Non hai podcasts nesta biblioteca."'),
|
||||
(S + "HomeActivity.java", "\"Can't reach the server. Showing saved episodes.\"",
|
||||
'"Non se pode acceder ao servidor. Amósanse os episodios gardados."'),
|
||||
(S + "HomeActivity.java", '"DOWNLOADING"', '"DESCARGANDO"'),
|
||||
(S + "HomeActivity.java", '"SAVED ON SD CARD ("', '"GARDADOS NA TARXETA SD ("'),
|
||||
(S + "HomeActivity.java", '"Nothing saved yet.\\n\\nLong-press an episode and choose Download to listen offline."',
|
||||
'"Aínda non gardaches nada.\\n\\nMantén premido un episodio e escolle Descargar para escoitalo sen conexión."'),
|
||||
(S + "HomeActivity.java", '"Loading…"', '"Cargando…"'),
|
||||
(S + "HomeActivity.java", '"Still loading libraries…"', '"Aínda se están cargando as bibliotecas…"'),
|
||||
(S + "HomeActivity.java", '"This is your only podcast library."', '"Esta é a túa única biblioteca de podcasts."'),
|
||||
(S + "HomeActivity.java", '"Podcast library"', '"Biblioteca de podcasts"'),
|
||||
(S + "HomeActivity.java", '"Refresh"', '"Actualizar"'),
|
||||
(S + "HomeActivity.java", '"Library"', '"Biblioteca"'),
|
||||
(S + "HomeActivity.java", '"Now playing"', '"En reprodución"'),
|
||||
(S + "HomeActivity.java", '"Settings"', '"Axustes"'),
|
||||
|
||||
# ---------------- LoginActivity
|
||||
(S + "LoginActivity.java", '"API key"', '"Chave da API"'),
|
||||
(S + "LoginActivity.java", '"Password"', '"Contrasinal"'),
|
||||
(S + "LoginActivity.java", '"Enter your server address."', '"Escribe o enderezo do servidor."'),
|
||||
(S + "LoginActivity.java", '"Paste your API key."', '"Pega a túa chave da API."'),
|
||||
(S + "LoginActivity.java", '"Enter your username."', '"Escribe o teu usuario."'),
|
||||
(S + "LoginActivity.java", '"Connecting…"', '"Conectando…"'),
|
||||
|
||||
# ---------------- PlayerActivity
|
||||
(S + "PlayerActivity.java", '"Nothing playing"', '"Non se está a reproducir nada"'),
|
||||
(S + "PlayerActivity.java", '"Pick an episode from the list."', '"Escolle un episodio da lista."'),
|
||||
(S + "PlayerActivity.java", '"Sleep at end"', '"Pausa ao final do episodio"'),
|
||||
(S + "PlayerActivity.java", '"Sleep in "', '"Pausa en "'),
|
||||
(S + "PlayerActivity.java", '"Connecting…"', '"Conectando…"'),
|
||||
(S + "PlayerActivity.java", '"Saved on SD card"', '"Gardado na tarxeta SD"'),
|
||||
(S + "PlayerActivity.java", '"Sleep timer"', '"Temporizador de pausa"'),
|
||||
(S + "PlayerActivity.java", '"Mark as played"', '"Marcar como escoitado"'),
|
||||
(S + "PlayerActivity.java", '"Cancel download"', '"Cancelar descarga"'),
|
||||
(S + "PlayerActivity.java", '"Delete download"', '"Eliminar descarga"'),
|
||||
(S + "PlayerActivity.java", '"Download"', '"Descargar"'),
|
||||
(S + "PlayerActivity.java", '"Go to show"', '"Ir ao programa"'),
|
||||
(S + "PlayerActivity.java", '"Stop"', '"Deter"'),
|
||||
(S + "PlayerActivity.java", '"Marked as played"', '"Marcado como escoitado"'),
|
||||
(S + "PlayerActivity.java", '"Off"', '"Desactivado"'),
|
||||
(S + "PlayerActivity.java", ' minutes"', ' minutos"'),
|
||||
(S + "PlayerActivity.java", '"1 hour"', '"1 hora"'),
|
||||
(S + "PlayerActivity.java", '"End of episode"', '"Ao final do episodio"'),
|
||||
(S + "PlayerActivity.java", '"Sleep timer off"', '"Temporizador desactivado"'),
|
||||
(S + "PlayerActivity.java", '"Sleep timer set"', '"Temporizador activado"'),
|
||||
|
||||
# ---------------- PlayerService
|
||||
(S + "PlayerService.java", '"Paused"', '"En pausa"'),
|
||||
(S + "PlayerService.java", '"Marked as played"', '"Marcado como escoitado"'),
|
||||
(S + "PlayerService.java", '"Connecting…"', '"Conectando…"'),
|
||||
(S + "PlayerService.java", '"The server sent no audio for this episode."', '"O servidor non enviou audio para este episodio."'),
|
||||
(S + "PlayerService.java", "\"This episode needs transcoding, which Android 2.x can't play. Try downloading it.\"",
|
||||
'"Este episodio precisa transcodificación, que Android 2.x non pode reproducir. Proba a descargalo."'),
|
||||
(S + "PlayerService.java", '"Offline · progress will sync later"', '"Sen conexión · o progreso sincronizarase despois"'),
|
||||
(S + "PlayerService.java", '"Opening saved file…"', '"Abrindo o ficheiro gardado…"'),
|
||||
(S + "PlayerService.java", '"Buffering…"', '"Cargando…"'),
|
||||
(S + "PlayerService.java", "\"Can't open the audio: \"", '"Non se pode abrir o audio: "'),
|
||||
(S + "PlayerService.java", '"Playback failed: "', '"Fallou a reprodución: "'),
|
||||
(S + "PlayerService.java", '"Saved file · offline"', '"Ficheiro gardado · sen conexión"'),
|
||||
(S + "PlayerService.java", '"Saved file"', '"Ficheiro gardado"'),
|
||||
(S + "PlayerService.java", '"Streaming"', '"Reproducindo en liña"'),
|
||||
(S + "PlayerService.java", '"Connection lost, reconnecting…"', '"Perdeuse a conexión, reconectando…"'),
|
||||
(S + "PlayerService.java", '"Finished"', '"Rematado"'),
|
||||
(S + "PlayerService.java", '"Playback error ("', '"Erro de reprodución ("'),
|
||||
(S + "PlayerService.java", '"Lost connection to the server. Press play to try again."',
|
||||
'"Perdeuse a conexión co servidor. Preme reproducir para tentalo de novo."'),
|
||||
(S + "PlayerService.java", '"Sleep timer: paused"', '"Temporizador: en pausa"'),
|
||||
(S + "PlayerService.java", '"Playing"', '"Reproducindo"'),
|
||||
(S + "PlayerService.java", '"Loading…"', '"Cargando…"'),
|
||||
(S + "PlayerService.java", '"Stopped: tap to retry"', '"Detido: toca para tentalo de novo"'),
|
||||
|
||||
# ---------------- PodcastActivity
|
||||
(S + "PodcastActivity.java", '"1 episode"', '"1 episodio"'),
|
||||
(S + "PodcastActivity.java", '" episodes"', '" episodios"'),
|
||||
(S + "PodcastActivity.java", '" PLAYED EPISODES HIDDEN"', '" EPISODIOS ESCOITADOS OCULTOS"'),
|
||||
(S + "PodcastActivity.java", '"No episodes."', '"Non hai episodios."'),
|
||||
(S + "PodcastActivity.java", '"Newest first"', '"Máis recentes primeiro"'),
|
||||
(S + "PodcastActivity.java", '"Oldest first"', '"Máis antigos primeiro"'),
|
||||
(S + "PodcastActivity.java", '"Show played"', '"Amosar escoitados"'),
|
||||
(S + "PodcastActivity.java", '"Hide played"', '"Ocultar escoitados"'),
|
||||
(S + "PodcastActivity.java", '"Refresh"', '"Actualizar"'),
|
||||
(S + "PodcastActivity.java", '"Now playing"', '"En reprodución"'),
|
||||
|
||||
# ---------------- Rows
|
||||
(S + "Rows.java", '"1 episode"', '"1 episodio"'),
|
||||
(S + "Rows.java", '" episodes"', '" episodios"'),
|
||||
(S + "Rows.java", '"Played"', '"Escoitado"'),
|
||||
(S + "Rows.java", 'Util.duration(Math.max(0, dur - cur)) + " left"', '"quedan " + Util.duration(Math.max(0, dur - cur))'),
|
||||
(S + "Rows.java", '"Saving "', '"Gardando "'),
|
||||
(S + "Rows.java", '"Queued"', '"Na cola"'),
|
||||
(S + "Rows.java", '"Saved"', '"Gardado"'),
|
||||
(S + "Rows.java", '"Now playing"', '"En reprodución"'),
|
||||
|
||||
# ---------------- SettingsActivity
|
||||
(S + "SettingsActivity.java", '" seconds"', '" segundos"'),
|
||||
(S + "SettingsActivity.java", '"API key"', '"Chave da API"'),
|
||||
(S + "SettingsActivity.java", '"Delete every downloaded episode from the SD card?"',
|
||||
'"Eliminar todos os episodios descargados da tarxeta SD?"'),
|
||||
(S + "SettingsActivity.java", '"Delete"', '"Eliminar"'),
|
||||
(S + "SettingsActivity.java", '" deleted"', '" eliminados"'),
|
||||
(S + "SettingsActivity.java", '"Cancel"', '"Cancelar"'),
|
||||
(S + "SettingsActivity.java", '"None"', '"Ningún"'),
|
||||
(S + "SettingsActivity.java", '" (tap to forget)"', '" (toca para esquecelos)"'),
|
||||
(S + "SettingsActivity.java", "\"Forget the certificates you chose to trust? You'll be asked again next time.\"",
|
||||
'"Esquecer os certificados nos que decidiches confiar? Volverase preguntar a próxima vez."'),
|
||||
(S + "SettingsActivity.java", '"Forget"', '"Esquecer"'),
|
||||
(S + "SettingsActivity.java", '"Log out of "', '"Pechar sesión en "'),
|
||||
(S + "SettingsActivity.java", '"? Downloads stay on the SD card."', '"? As descargas quedan na tarxeta SD."'),
|
||||
(S + "SettingsActivity.java", '"Log out"', '"Pechar sesión"'),
|
||||
]
|
||||
|
||||
missing = []
|
||||
for f, old, new in R:
|
||||
p = os.path.join(ROOT, f)
|
||||
s = open(p, encoding="utf-8").read()
|
||||
if old not in s:
|
||||
missing.append((f, old[:70]))
|
||||
continue
|
||||
open(p, "w", encoding="utf-8").write(s.replace(old, new))
|
||||
if missing:
|
||||
for m in missing:
|
||||
print("NOT FOUND", m)
|
||||
sys.exit(1)
|
||||
print(len(R), "replacements applied")
|
||||