Compare commits
	
		
			9 Commits
		
	
	
		
			0.1-alpha1
			...
			0.1-alpha2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cbfd186686 | |||
| 5b7d2cd26e | |||
| 6fb8f56faf | |||
| dcaf64acde | |||
| 597271d4de | |||
| c947105a1f | |||
| 9ec4c24e21 | |||
| 00a6981ae5 | |||
| ee063a5bbe | 
| @ -13,7 +13,7 @@ A unoffical App for Anime-on-Demand. | |||||||
| [<img src="https://www.mosad.xyz/images/Teapod/Teapod_Search.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Search.png) | [<img src="https://www.mosad.xyz/images/Teapod/Teapod_Search.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Search.png) | ||||||
|  |  | ||||||
| ## License | ## License | ||||||
| This App is licensed under the treams and conditions of GPL3. This Project is not accosiated with Anime-on-Demand in anya way. | This App is licensed under the terms and conditions of GPL 3. This Project is not associated with Anime-on-Demand in any way. | ||||||
|  |  | ||||||
| ### Used Libraries | ### Used Libraries | ||||||
| * gson: https://github.com/google/gson | * gson: https://github.com/google/gson | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ android { | |||||||
|         minSdkVersion 23 |         minSdkVersion 23 | ||||||
|         targetSdkVersion 30 |         targetSdkVersion 30 | ||||||
|         versionCode 1 |         versionCode 1 | ||||||
|         versionName "0.1-alpha1" |         versionName "0.1-alpha2" | ||||||
|  |  | ||||||
|         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||||
|         resValue "string", "build_time", buildTime() |         resValue "string", "build_time", buildTime() | ||||||
| @ -41,11 +41,10 @@ dependencies { | |||||||
|     implementation 'androidx.core:core-ktx:1.3.2' |     implementation 'androidx.core:core-ktx:1.3.2' | ||||||
|     implementation 'androidx.appcompat:appcompat:1.2.0' |     implementation 'androidx.appcompat:appcompat:1.2.0' | ||||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.0.2' |     implementation 'androidx.constraintlayout:constraintlayout:2.0.2' | ||||||
|     implementation 'androidx.navigation:navigation-fragment:2.3.0' |  | ||||||
|     implementation 'androidx.navigation:navigation-ui:2.3.0' |  | ||||||
|     implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' |     implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' | ||||||
|     implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' |     implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' | ||||||
|     implementation 'androidx.security:security-crypto:1.1.0-alpha02' |     implementation 'androidx.security:security-crypto:1.1.0-alpha02' | ||||||
|  |     implementation 'androidx.legacy:legacy-support-v4:1.0.0' | ||||||
|  |  | ||||||
|     implementation 'com.google.android.material:material:1.2.1' |     implementation 'com.google.android.material:material:1.2.1' | ||||||
|     implementation 'com.google.code.gson:gson:2.8.6' |     implementation 'com.google.code.gson:gson:2.8.6' | ||||||
| @ -56,9 +55,10 @@ dependencies { | |||||||
|  |  | ||||||
|     implementation 'org.jsoup:jsoup:1.13.1' |     implementation 'org.jsoup:jsoup:1.13.1' | ||||||
|     implementation 'com.github.bumptech.glide:glide:4.11.0' |     implementation 'com.github.bumptech.glide:glide:4.11.0' | ||||||
|  |     implementation 'jp.wasabeef:glide-transformations:4.3.0' | ||||||
|     implementation 'com.afollestad.material-dialogs:core:3.3.0' |     implementation 'com.afollestad.material-dialogs:core:3.3.0' | ||||||
|     implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0' |     implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0' | ||||||
|     implementation 'androidx.legacy:legacy-support-v4:1.0.0' |     implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0' | ||||||
|  |  | ||||||
|     testImplementation 'junit:junit:4.12' |     testImplementation 'junit:junit:4.12' | ||||||
|     androidTestImplementation 'androidx.test.ext:junit:1.1.2' |     androidTestImplementation 'androidx.test.ext:junit:1.1.2' | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
|             android:name=".PlayerActivity" |             android:name=".PlayerActivity" | ||||||
|             android:label="@string/app_name" |             android:label="@string/app_name" | ||||||
|             android:configChanges="orientation|screenSize|layoutDirection" |             android:configChanges="orientation|screenSize|layoutDirection" | ||||||
|             android:theme="@style/AppTheme.AppCompat.Light.NoActionBar.FullScreen" /> |             android:theme="@style/AppTheme.MaterialComponents.Light.NoActionBar.FullScreen" /> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".MainActivity" |             android:name=".MainActivity" | ||||||
|             android:label="@string/app_name" |             android:label="@string/app_name" | ||||||
|  | |||||||
| @ -1,3 +1,25 @@ | |||||||
|  | /** | ||||||
|  |  * Teapod | ||||||
|  |  * | ||||||
|  |  * Copyright 2020  <seil0@mosad.xyz> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program; if not, write to the Free Software | ||||||
|  |  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||||||
|  |  * MA 02110-1301, USA. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  |  | ||||||
| package org.mosad.teapod | package org.mosad.teapod | ||||||
|  |  | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
| @ -9,6 +31,8 @@ import androidx.appcompat.app.AppCompatActivity | |||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.commit | import androidx.fragment.app.commit | ||||||
| import kotlinx.android.synthetic.main.activity_main.* | import kotlinx.android.synthetic.main.activity_main.* | ||||||
|  | import kotlinx.coroutines.GlobalScope | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import org.mosad.teapod.parser.AoDParser | import org.mosad.teapod.parser.AoDParser | ||||||
| import org.mosad.teapod.preferences.EncryptedPreferences | import org.mosad.teapod.preferences.EncryptedPreferences | ||||||
| import org.mosad.teapod.ui.MediaFragment | import org.mosad.teapod.ui.MediaFragment | ||||||
| @ -76,19 +100,18 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS | |||||||
|     private fun load() { |     private fun load() { | ||||||
|         EncryptedPreferences.readCredentials(this) |         EncryptedPreferences.readCredentials(this) | ||||||
|  |  | ||||||
|  |         // make sure credentials are set and valid | ||||||
|         if (EncryptedPreferences.password.isEmpty()) { |         if (EncryptedPreferences.password.isEmpty()) { | ||||||
|             Log.i(javaClass.name, "please login!") |             showLoginDialog(true) | ||||||
|  |         } else if (!AoDParser().login()) { | ||||||
|             LoginDialog(this).positiveButton { |             showLoginDialog(false) | ||||||
|                 EncryptedPreferences.saveCredentials(login, password, context) |  | ||||||
|             }.negativeButton { |  | ||||||
|                 Log.i(javaClass.name, "Login canceled, exiting.") |  | ||||||
|                 finish() |  | ||||||
|             }.show() |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun showDetailFragment(media: Media) { |     /** | ||||||
|  |      * TODO show loading fragment | ||||||
|  |      */ | ||||||
|  |     fun showDetailFragment(media: Media) = GlobalScope.launch { | ||||||
|         media.episodes = AoDParser().loadStreams(media) // load the streams for the selected media |         media.episodes = AoDParser().loadStreams(media) // load the streams for the selected media | ||||||
|  |  | ||||||
|         val tmdb = TMDBApiController().search(media.title, media.type) |         val tmdb = TMDBApiController().search(media.title, media.type) | ||||||
| @ -107,4 +130,18 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS | |||||||
|         } |         } | ||||||
|         startActivity(intent) |         startActivity(intent) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun showLoginDialog(firstTry: Boolean) { | ||||||
|  |         LoginDialog(this, firstTry).positiveButton { | ||||||
|  |             EncryptedPreferences.saveCredentials(login, password, context) | ||||||
|  |  | ||||||
|  |             if (!AoDParser().login()) { | ||||||
|  |                 showLoginDialog(false) | ||||||
|  |                 Log.w(javaClass.name, "Login failed, please try again.") | ||||||
|  |             } | ||||||
|  |         }.negativeButton { | ||||||
|  |             Log.i(javaClass.name, "Login canceled, exiting.") | ||||||
|  |             finish() | ||||||
|  |         }.show() | ||||||
|  |     } | ||||||
| } | } | ||||||
| @ -25,7 +25,7 @@ class AoDParser { | |||||||
|         val mediaList = arrayListOf<Media>() |         val mediaList = arrayListOf<Media>() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun login() = runBlocking { |     fun login(): Boolean = runBlocking { | ||||||
|  |  | ||||||
|         val userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0" |         val userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0" | ||||||
|  |  | ||||||
| @ -36,10 +36,10 @@ class AoDParser { | |||||||
|                 .execute() |                 .execute() | ||||||
|  |  | ||||||
|             val authenticityToken = resAuth.parse().select("meta[name=csrf-token]").attr("content") |             val authenticityToken = resAuth.parse().select("meta[name=csrf-token]").attr("content") | ||||||
|             println("Authenticity token is: $authenticityToken") |             val authCookies = resAuth.cookies() | ||||||
|  |  | ||||||
|             val cookies = resAuth.cookies() |             Log.i(javaClass.name, "Received authenticity token: $authenticityToken") | ||||||
|             println("cookies: $cookies") |             Log.i(javaClass.name, "Received authenticity cookies: $authCookies") | ||||||
|  |  | ||||||
|             val data = mapOf( |             val data = mapOf( | ||||||
|                 Pair("user[login]", EncryptedPreferences.login), |                 Pair("user[login]", EncryptedPreferences.login), | ||||||
| @ -53,15 +53,16 @@ class AoDParser { | |||||||
|                 .method(Connection.Method.POST) |                 .method(Connection.Method.POST) | ||||||
|                 .data(data) |                 .data(data) | ||||||
|                 .postDataCharset("UTF-8") |                 .postDataCharset("UTF-8") | ||||||
|                 .cookies(cookies) |                 .cookies(authCookies) | ||||||
|                 .execute() |                 .execute() | ||||||
|  |  | ||||||
|             //println(resLogin.body()) |             //println(resLogin.body()) | ||||||
|  |  | ||||||
|             loginSuccess = resLogin.body().contains("Hallo, du bist jetzt angemeldet.") |  | ||||||
|             println("Status: ${resLogin.statusCode()} (${resLogin.statusMessage()}), login successful: $loginSuccess") |  | ||||||
|  |  | ||||||
|             sessionCookies = resLogin.cookies() |             sessionCookies = resLogin.cookies() | ||||||
|  |             loginSuccess = resLogin.body().contains("Hallo, du bist jetzt angemeldet.") | ||||||
|  |  | ||||||
|  |             Log.i(javaClass.name, "Status: ${resLogin.statusCode()} (${resLogin.statusMessage()}), login successful: $loginSuccess") | ||||||
|  |  | ||||||
|  |             loginSuccess | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -89,14 +90,15 @@ class AoDParser { | |||||||
|                 val media = Media( |                 val media = Media( | ||||||
|                     it.select("h3.animebox-title").text(), |                     it.select("h3.animebox-title").text(), | ||||||
|                     it.select("p.animebox-link").select("a").attr("href"), |                     it.select("p.animebox-link").select("a").attr("href"), | ||||||
|                     type, |                     type | ||||||
|                     it.select("p.animebox-image").select("img").attr("src"), |  | ||||||
|                     it.select("p.animebox-shorttext").text() |  | ||||||
|                 ) |                 ) | ||||||
|  |                 media.info.posterLink = it.select("p.animebox-image").select("img").attr("src") | ||||||
|  |                 media.info.shortDesc = it.select("p.animebox-shorttext").text() | ||||||
|  |  | ||||||
|                 mediaList.add(media) |                 mediaList.add(media) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             println("got ${mediaList.size} anime") |             Log.i(javaClass.name, "Total library size is: ${mediaList.size}") | ||||||
|  |  | ||||||
|             return@withContext mediaList |             return@withContext mediaList | ||||||
|         } |         } | ||||||
| @ -109,7 +111,7 @@ class AoDParser { | |||||||
|         if (sessionCookies.isEmpty()) login() |         if (sessionCookies.isEmpty()) login() | ||||||
|  |  | ||||||
|         if (!loginSuccess) { |         if (!loginSuccess) { | ||||||
|             println("please log in") // TODO |             Log.w(javaClass.name, "Login, was not successful.") | ||||||
|             return@runBlocking listOf() |             return@runBlocking listOf() | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @ -121,20 +123,51 @@ class AoDParser { | |||||||
|  |  | ||||||
|             //println(res) |             //println(res) | ||||||
|  |  | ||||||
|  |             // parse additional info from the media page | ||||||
|  |             res.select("table.vertical-table").select("tr").forEach { | ||||||
|  |                 when (it.select("th").text().toLowerCase(Locale.ROOT)) { | ||||||
|  |                     "produktionsjahr" -> media.info.year = it.select("td").text().toInt() | ||||||
|  |                     "fsk" -> media.info.age = it.select("td").text().toInt() | ||||||
|  |                     "episodenanzahl" -> media.info.episodesCount = it.select("td").text().toInt() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * TODO tv show specific for each episode (div.episodebox) | ||||||
|  |              *  * watchedCallback | ||||||
|  |              */ | ||||||
|  |             val episodes = if (media.type == MediaType.TVSHOW) { | ||||||
|  |                 res.select("div.three-box-container > div.episodebox").map { episodebox -> | ||||||
|  |                     val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt() | ||||||
|  |                     val episodeWatched = episodebox.select("div.episodebox-icons > div").hasClass("status-icon-orange") | ||||||
|  |                     val episodeShortDesc = episodebox.select("p.episodebox-shorttext").text() | ||||||
|  |  | ||||||
|  |                     Episode(id = episodeId, watched = episodeWatched, shortDesc = episodeShortDesc) | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 listOf(Episode()) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // has attr data-lag (ger or jap) | ||||||
|             val playlists = res.select("input.streamstarter_html5").eachAttr("data-playlist") |             val playlists = res.select("input.streamstarter_html5").eachAttr("data-playlist") | ||||||
|             val csrfToken = res.select("meta[name=csrf-token]").attr("content") |             val csrfToken = res.select("meta[name=csrf-token]").attr("content") | ||||||
|  |  | ||||||
|             //println("first entry: ${playlists.first()}") |             //println("first entry: ${playlists.first()}") | ||||||
|             //println("csrf token is: $csrfToken") |             //println("csrf token is: $csrfToken") | ||||||
|  |  | ||||||
|             return@withContext loadStreamInfo(playlists.first(), csrfToken, media.type) |             return@withContext if (playlists.size > 0) { | ||||||
|  |                 loadStreamInfo(playlists.first(), csrfToken, media.type, episodes) | ||||||
|  |             } else { | ||||||
|  |                 listOf() | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * load the playlist path and parse it, read the stream info from json |      * load the playlist path and parse it, read the stream info from json | ||||||
|  |      * @param episodes is used as call ba reference, additionally it is passed a return value | ||||||
|      */ |      */ | ||||||
|     private fun loadStreamInfo(playlistPath: String, csrfToken: String, type: MediaType): List<Episode> = runBlocking { |     private fun loadStreamInfo(playlistPath: String, csrfToken: String, type: MediaType, episodes: List<Episode>): List<Episode> = runBlocking { | ||||||
|         withContext(Dispatchers.Default) { |         withContext(Dispatchers.Default) { | ||||||
|             val headers = mutableMapOf( |             val headers = mutableMapOf( | ||||||
|                 Pair("Accept", "application/json, text/javascript, */*; q=0.01"), |                 Pair("Accept", "application/json, text/javascript, */*; q=0.01"), | ||||||
| @ -152,38 +185,48 @@ class AoDParser { | |||||||
|  |  | ||||||
|             //println(res.body()) |             //println(res.body()) | ||||||
|  |  | ||||||
|             return@withContext when (type) { |             when (type) { | ||||||
|                 MediaType.MOVIE -> { |                 MediaType.MOVIE -> { | ||||||
|                     val movie = JsonParser.parseString(res.body()).asJsonObject |                     val movie = JsonParser.parseString(res.body()).asJsonObject | ||||||
|                         .get("playlist").asJsonArray |                         .get("playlist").asJsonArray | ||||||
|  |  | ||||||
|                     movie.first().asJsonObject.get("sources").asJsonArray.toList().map { |                     movie.first().asJsonObject.get("sources").asJsonArray.toList().forEach { | ||||||
|                         Episode(streamUrl = it.asJsonObject.get("file").asString) |                         episodes.first().streamUrl = it.asJsonObject.get("file").asString | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 MediaType.TVSHOW -> { |                 MediaType.TVSHOW -> { | ||||||
|                     val episodesJson = JsonParser.parseString(res.body()).asJsonObject |                     val episodesJson = JsonParser.parseString(res.body()).asJsonObject | ||||||
|                         .get("playlist").asJsonArray |                         .get("playlist").asJsonArray | ||||||
|  |  | ||||||
|  |  | ||||||
|                     episodesJson.map { |                     episodesJson.forEach { jsonElement -> | ||||||
|                         val episodeStream = it.asJsonObject.get("sources").asJsonArray |                         val episodeId = jsonElement.asJsonObject.get("mediaid") | ||||||
|  |                         val episodeStream = jsonElement.asJsonObject.get("sources").asJsonArray | ||||||
|                             .first().asJsonObject |                             .first().asJsonObject | ||||||
|                             .get("file").asString |                             .get("file").asString | ||||||
|                         val episodeTitle = it.asJsonObject.get("title").asString |                         val episodeTitle = jsonElement.asJsonObject.get("title").asString | ||||||
|  |                         val episodePoster = jsonElement.asJsonObject.get("image").asString | ||||||
|  |                         val episodeDescription = jsonElement.asJsonObject.get("description").asString | ||||||
|  |                         val episodeNumber = episodeTitle.substringAfter(", Ep. ").toInt() | ||||||
|  |  | ||||||
|                         Episode( |                         episodes.first { it.id == episodeId.asInt }.apply { | ||||||
|                             episodeTitle, |                             this.title = episodeTitle | ||||||
|                             episodeStream |                             this.posterLink = episodePoster | ||||||
|                         ) |                             this.streamUrl = episodeStream | ||||||
|  |                             this.description = episodeDescription | ||||||
|  |                             this.number = episodeNumber | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 else -> { |                 else -> { | ||||||
|                     Log.e(javaClass.name, "Wrong Type, please report this issue.") |                     Log.e(javaClass.name, "Wrong Type, please report this issue.") | ||||||
|                     listOf() |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             return@withContext episodes | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| package org.mosad.teapod.ui | package org.mosad.teapod.ui | ||||||
|  |  | ||||||
|  | import android.graphics.Color | ||||||
|  | import android.graphics.drawable.ColorDrawable | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.util.Log | import android.util.Log | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| @ -9,6 +11,8 @@ import android.view.ViewGroup | |||||||
| import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
| import com.bumptech.glide.Glide | import com.bumptech.glide.Glide | ||||||
|  | import com.bumptech.glide.request.RequestOptions | ||||||
|  | import jp.wasabeef.glide.transformations.BlurTransformation | ||||||
| import kotlinx.android.synthetic.main.fragment_media.* | import kotlinx.android.synthetic.main.fragment_media.* | ||||||
| import org.mosad.teapod.MainActivity | import org.mosad.teapod.MainActivity | ||||||
| import org.mosad.teapod.R | import org.mosad.teapod.R | ||||||
| @ -30,36 +34,48 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) : | |||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|  |  | ||||||
|         // generic gui |         initGUI() | ||||||
|         text_title.text = media.title |         initActions() | ||||||
|  |  | ||||||
|         if (tmdb.posterUrl.isNotEmpty()) { |  | ||||||
|             Glide.with(requireContext()).load(tmdb.posterUrl).into(image_poster) |  | ||||||
|             text_desc.text = tmdb.overview |  | ||||||
|             Log.d(javaClass.name, "TMDB data present") |  | ||||||
|         } else { |  | ||||||
|             Glide.with(requireContext()).load(media.posterLink).into(image_poster) |  | ||||||
|             text_desc.text = media.shortDesc |  | ||||||
|             Log.d(javaClass.name, "No TMDB data present, using Aod") |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * if tmdb data is present, use it, else use the aod data | ||||||
|  |      */ | ||||||
|  |     private fun initGUI() { | ||||||
|  |         // generic gui | ||||||
|  |         val backdropUrl = if (tmdb.backdropUrl.isNotEmpty()) tmdb.backdropUrl else media.info.posterLink | ||||||
|  |         val posterUrl = if (tmdb.posterUrl.isNotEmpty()) tmdb.posterUrl else media.info.posterLink | ||||||
|  |  | ||||||
|  |         Glide.with(requireContext()).load(backdropUrl) | ||||||
|  |             .apply(RequestOptions.placeholderOf(ColorDrawable(Color.DKGRAY))) | ||||||
|  |             .apply(RequestOptions.bitmapTransform(BlurTransformation(25, 3))) | ||||||
|  |             .into(image_backdrop) | ||||||
|  |  | ||||||
|  |         Glide.with(requireContext()).load(posterUrl) | ||||||
|  |             .into(image_poster) | ||||||
|  |  | ||||||
|  |         text_title.text = media.title | ||||||
|  |         text_year.text = media.info.year.toString() | ||||||
|  |         text_age.text = media.info.age.toString() | ||||||
|  |         text_overview.text = media.info.shortDesc //if (tmdb.overview.isNotEmpty()) tmdb.overview else media.shortDesc | ||||||
|  |  | ||||||
|         // specific gui |         // specific gui | ||||||
|         if (media.type == MediaType.TVSHOW) { |         if (media.type == MediaType.TVSHOW) { | ||||||
|             val episodeTitles = media.episodes.map { it.title } |             adapterRecEpisodes = EpisodesAdapter(media.episodes, requireContext()) | ||||||
|  |  | ||||||
|             adapterRecEpisodes = EpisodesAdapter(episodeTitles) |  | ||||||
|             viewManager = LinearLayoutManager(context) |             viewManager = LinearLayoutManager(context) | ||||||
|             recycler_episodes.layoutManager = viewManager |             recycler_episodes.layoutManager = viewManager | ||||||
|             recycler_episodes.adapter = adapterRecEpisodes |             recycler_episodes.adapter = adapterRecEpisodes | ||||||
|  |  | ||||||
|  |             text_episodes_or_runtime.text = getString(R.string.text_episodes_count, media.info.episodesCount) | ||||||
|         } else if (media.type == MediaType.MOVIE) { |         } else if (media.type == MediaType.MOVIE) { | ||||||
|             recycler_episodes.visibility = View.GONE |             recycler_episodes.visibility = View.GONE | ||||||
|  |  | ||||||
|  |             if (tmdb.runtime > 0) { | ||||||
|  |                 text_episodes_or_runtime.text = getString(R.string.text_runtime, tmdb.runtime) | ||||||
|  |             } else { | ||||||
|  |                 text_episodes_or_runtime.visibility = View.GONE | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         println("media streams: ${media.episodes}") |  | ||||||
|  |  | ||||||
|         initActions() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun initActions() { |     private fun initActions() { | ||||||
| @ -67,13 +83,13 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) : | |||||||
|             when (media.type) { |             when (media.type) { | ||||||
|                 MediaType.MOVIE -> playStream(media.episodes.first().streamUrl) |                 MediaType.MOVIE -> playStream(media.episodes.first().streamUrl) | ||||||
|                 MediaType.TVSHOW -> playStream(media.episodes.first().streamUrl) |                 MediaType.TVSHOW -> playStream(media.episodes.first().streamUrl) | ||||||
|                 MediaType.OTHER -> Log.e(javaClass.name, "Wrong Type, please report this issue.") |                 else -> Log.e(javaClass.name, "Wrong Type: $media.type") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // set onItemClick only in adapter is initialized |         // set onItemClick only in adapter is initialized | ||||||
|         if (this::adapterRecEpisodes.isInitialized) { |         if (this::adapterRecEpisodes.isInitialized) { | ||||||
|             adapterRecEpisodes.onItemClick = { item, position -> |             adapterRecEpisodes.onItemClick = { _, position -> | ||||||
|                 playStream(media.episodes[position].streamUrl) |                 playStream(media.episodes[position].streamUrl) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,14 +1,17 @@ | |||||||
| package org.mosad.teapod.ui.account | package org.mosad.teapod.ui.account | ||||||
|  |  | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
|  | import android.util.Log | ||||||
| import android.view.LayoutInflater | import android.view.LayoutInflater | ||||||
| import android.view.View | import android.view.View | ||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import com.afollestad.materialdialogs.MaterialDialog | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||||
|  | import de.psdev.licensesdialog.LicensesDialog | ||||||
| import kotlinx.android.synthetic.main.fragment_account.* | import kotlinx.android.synthetic.main.fragment_account.* | ||||||
| import org.mosad.teapod.BuildConfig | import org.mosad.teapod.BuildConfig | ||||||
| import org.mosad.teapod.R | import org.mosad.teapod.R | ||||||
|  | import org.mosad.teapod.parser.AoDParser | ||||||
| import org.mosad.teapod.preferences.EncryptedPreferences | import org.mosad.teapod.preferences.EncryptedPreferences | ||||||
| import org.mosad.teapod.ui.components.LoginDialog | import org.mosad.teapod.ui.components.LoginDialog | ||||||
|  |  | ||||||
| @ -29,19 +32,38 @@ class AccountFragment : Fragment() { | |||||||
|  |  | ||||||
|     private fun initActions() { |     private fun initActions() { | ||||||
|         linear_account_login.setOnClickListener { |         linear_account_login.setOnClickListener { | ||||||
|             LoginDialog(requireContext()).positiveButton { |             showLoginDialog(true) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         linear_about.setOnClickListener { | ||||||
|  |             MaterialAlertDialogBuilder(requireContext()) | ||||||
|  |                 .setTitle(R.string.info_about) | ||||||
|  |                 .setMessage(R.string.info_about_dialog) | ||||||
|  |                 .show() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         text_licenses.setOnClickListener { | ||||||
|  |             LicensesDialog.Builder(requireContext()) | ||||||
|  |                 .setNotices(R.raw.notices) | ||||||
|  |                 .setTitle(R.string.licenses) | ||||||
|  |                 .setIncludeOwnLicense(true) | ||||||
|  |                 .setThemeResourceId(R.style.AppTheme) | ||||||
|  |                 .build() | ||||||
|  |                 .show() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun showLoginDialog(firstTry: Boolean) { | ||||||
|  |         LoginDialog(requireContext(), firstTry).positiveButton { | ||||||
|             EncryptedPreferences.saveCredentials(login, password, context) |             EncryptedPreferences.saveCredentials(login, password, context) | ||||||
|  |  | ||||||
|  |             if (!AoDParser().login()) { | ||||||
|  |                 showLoginDialog(false) | ||||||
|  |                 Log.w(javaClass.name, "Login failed, please try again.") | ||||||
|  |             } | ||||||
|         }.show { |         }.show { | ||||||
|             login = EncryptedPreferences.login |             login = EncryptedPreferences.login | ||||||
|             password = "" |             password = "" | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         linear_about.setOnClickListener { |  | ||||||
|             MaterialDialog(requireContext()) |  | ||||||
|                 .title(R.string.info_about) |  | ||||||
|                 .message(R.string.info_about_dialog) |  | ||||||
|                 .show() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @ -31,7 +31,7 @@ import com.afollestad.materialdialogs.customview.customView | |||||||
| import com.afollestad.materialdialogs.customview.getCustomView | import com.afollestad.materialdialogs.customview.getCustomView | ||||||
| import org.mosad.teapod.R | import org.mosad.teapod.R | ||||||
|  |  | ||||||
| class LoginDialog(val context: Context) { | class LoginDialog(val context: Context, firstTry: Boolean) { | ||||||
|  |  | ||||||
|     private val dialog = MaterialDialog(context, BottomSheet()) |     private val dialog = MaterialDialog(context, BottomSheet()) | ||||||
|  |  | ||||||
| @ -43,7 +43,7 @@ class LoginDialog(val context: Context) { | |||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         dialog.title(R.string.login) |         dialog.title(R.string.login) | ||||||
|             .message(R.string.login_desc) |             .message(if (firstTry) R.string.login_desc else R.string.login_failed_desc) | ||||||
|             .customView(R.layout.dialog_login) |             .customView(R.layout.dialog_login) | ||||||
|             .positiveButton(R.string.save) |             .positiveButton(R.string.save) | ||||||
|             .negativeButton(R.string.cancel) |             .negativeButton(R.string.cancel) | ||||||
|  | |||||||
| @ -16,8 +16,7 @@ import org.mosad.teapod.util.Media | |||||||
|  |  | ||||||
| class SearchFragment : Fragment() { | class SearchFragment : Fragment() { | ||||||
|  |  | ||||||
|     private val instance = this |     private var adapter : CustomAdapter? = null | ||||||
|     private lateinit var adapter : CustomAdapter |  | ||||||
|  |  | ||||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { | ||||||
|         return inflater.inflate(R.layout.fragment_search, container, false) |         return inflater.inflate(R.layout.fragment_search, container, false) | ||||||
| @ -46,23 +45,27 @@ class SearchFragment : Fragment() { | |||||||
|     private fun initActions() { |     private fun initActions() { | ||||||
|         search_text.setOnQueryTextListener(object : SearchView.OnQueryTextListener { |         search_text.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | ||||||
|             override fun onQueryTextSubmit(query: String?): Boolean { |             override fun onQueryTextSubmit(query: String?): Boolean { | ||||||
|  |                 adapter?.filter?.filter(query) | ||||||
|  |                 adapter?.notifyDataSetChanged() | ||||||
|                 return false |                 return false | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             override fun onQueryTextChange(newText: String?): Boolean { |             override fun onQueryTextChange(newText: String?): Boolean { | ||||||
|                 adapter.filter.filter(newText) |                 adapter?.filter?.filter(newText) | ||||||
|                 adapter.notifyDataSetChanged() |                 adapter?.notifyDataSetChanged() | ||||||
|                 return false |                 return false | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|         list_search.setOnItemClickListener { _, _, position, _ -> |         list_search.setOnItemClickListener { _, _, position, _ -> | ||||||
|             val media = adapter.getItem(position) as Media |             search_text.clearFocus() // remove focus from the SearchView | ||||||
|  |  | ||||||
|  |             runBlocking { | ||||||
|  |                 val media = adapter?.getItem(position) as Media | ||||||
|                 println("selected item is: ${media.title}") |                 println("selected item is: ${media.title}") | ||||||
|  |  | ||||||
|             val mainActivity = activity as MainActivity |                 (activity as MainActivity).showDetailFragment(media).join() | ||||||
|             mainActivity.showDetailFragment(media) |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -21,7 +21,7 @@ class CustomAdapter(val context: Context, private val originalMedia: ArrayList<M | |||||||
|         val imagePoster = view.findViewById<ImageView>(R.id.image_poster) |         val imagePoster = view.findViewById<ImageView>(R.id.image_poster) | ||||||
|  |  | ||||||
|         textTitle.text = filteredMedia[position].title |         textTitle.text = filteredMedia[position].title | ||||||
|         Glide.with(context).load(filteredMedia[position].posterLink).into(imagePoster) |         Glide.with(context).load(filteredMedia[position].info.posterLink).into(imagePoster) | ||||||
|  |  | ||||||
|         return view |         return view | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -8,12 +8,37 @@ class DataTypes { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| data class Media(val title: String, val link: String, val type: DataTypes.MediaType, val posterLink: String, val shortDesc : String, var episodes: List<Episode> = listOf()) { | data class Media(val title: String, val link: String, val type: DataTypes.MediaType, val info : Info = Info(), var episodes: List<Episode> = listOf()) { | ||||||
|     override fun toString(): String { |     override fun toString(): String { | ||||||
|         return title |         return title | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| data class Episode(val title: String = "", val streamUrl: String = "", val posterLink: String = "", var watched: Boolean = false) | data class Info( | ||||||
|  |     var posterLink: String = "", | ||||||
|  |     var shortDesc: String = "", | ||||||
|  |     var description: String = "", | ||||||
|  |     var year: Int = 0, | ||||||
|  |     var age: Int = 0, | ||||||
|  |     var episodesCount: Int = 0 | ||||||
|  | ) | ||||||
|  |  | ||||||
| data class TMDBResponse(val title: String = "", val overview: String = "", val posterUrl: String = "", val backdropUrl: String = "") | data class Episode( | ||||||
|  |     val id: Int = 0, | ||||||
|  |     var title: String = "", | ||||||
|  |     var streamUrl: String = "", | ||||||
|  |     var posterLink: String = "", | ||||||
|  |     var description: String = "", | ||||||
|  |     var shortDesc: String = "", | ||||||
|  |     var number: Int = 0, | ||||||
|  |     var watched: Boolean = false | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | data class TMDBResponse( | ||||||
|  |     val id: Int = 0, | ||||||
|  |     val title: String = "", | ||||||
|  |     val overview: String = "", | ||||||
|  |     val posterUrl: String = "", | ||||||
|  |     val backdropUrl: String = "", | ||||||
|  |     var runtime: Int = 0 | ||||||
|  | ) | ||||||
|  | |||||||
| @ -1,13 +1,15 @@ | |||||||
| package org.mosad.teapod.util | package org.mosad.teapod.util | ||||||
|  |  | ||||||
|  | import android.content.Context | ||||||
| import android.view.LayoutInflater | import android.view.LayoutInflater | ||||||
| import android.view.View | import android.view.View | ||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
|  | import com.bumptech.glide.Glide | ||||||
| import kotlinx.android.synthetic.main.component_episode.view.* | import kotlinx.android.synthetic.main.component_episode.view.* | ||||||
| import org.mosad.teapod.R | import org.mosad.teapod.R | ||||||
|  |  | ||||||
| class EpisodesAdapter(private val data: List<String>) : RecyclerView.Adapter<EpisodesAdapter.MyViewHolder>() { | class EpisodesAdapter(private val episodes: List<Episode>, private val context: Context) : RecyclerView.Adapter<EpisodesAdapter.MyViewHolder>() { | ||||||
|  |  | ||||||
|     var onItemClick: ((String, Int) -> Unit)? = null |     var onItemClick: ((String, Int) -> Unit)? = null | ||||||
|  |  | ||||||
| @ -18,17 +20,30 @@ class EpisodesAdapter(private val data: List<String>) : RecyclerView.Adapter<Epi | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun onBindViewHolder(holder: MyViewHolder, position: Int) { |     override fun onBindViewHolder(holder: MyViewHolder, position: Int) { | ||||||
|         holder.view .text_episode_title.text = data[position] |         holder.view.text_episode_title.text = context.getString( | ||||||
|  |             R.string.component_episode_title, | ||||||
|  |             episodes[position].number, | ||||||
|  |             episodes[position].description | ||||||
|  |         ) | ||||||
|  |         holder.view.text_episode_desc.text = episodes[position].shortDesc | ||||||
|  |  | ||||||
|  |         if (episodes[position].posterLink.isNotEmpty()) { | ||||||
|  |             Glide.with(context).load(episodes[position].posterLink).into(holder.view.image_episode) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!episodes[position].watched) { | ||||||
|  |             holder.view.image_watched.setImageDrawable(null) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getItemCount(): Int { |     override fun getItemCount(): Int { | ||||||
|         return data.size |         return episodes.size | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     inner class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view) { |     inner class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view) { | ||||||
|         init { |         init { | ||||||
|             view.setOnClickListener { |             view.setOnClickListener { | ||||||
|                 onItemClick?.invoke(data[adapterPosition], adapterPosition) |                 onItemClick?.invoke(episodes[adapterPosition].title, adapterPosition) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package org.mosad.teapod.util | package org.mosad.teapod.util | ||||||
|  |  | ||||||
| import android.util.Log | import android.util.Log | ||||||
|  | import com.google.gson.JsonObject | ||||||
| import com.google.gson.JsonParser | import com.google.gson.JsonParser | ||||||
| import kotlinx.coroutines.GlobalScope | import kotlinx.coroutines.GlobalScope | ||||||
| import kotlinx.coroutines.async | import kotlinx.coroutines.async | ||||||
| @ -14,26 +15,21 @@ class TMDBApiController { | |||||||
|     private val apiUrl = "https://api.themoviedb.org/3" |     private val apiUrl = "https://api.themoviedb.org/3" | ||||||
|     private val searchMovieUrl = "$apiUrl/search/movie" |     private val searchMovieUrl = "$apiUrl/search/movie" | ||||||
|     private val searchTVUrl = "$apiUrl/search/tv" |     private val searchTVUrl = "$apiUrl/search/tv" | ||||||
|  |     private val getMovieUrl = "$apiUrl/movie" | ||||||
|     private val apiKey = "de959cf9c07a08b5ca7cb51cda9a40c2" |     private val apiKey = "de959cf9c07a08b5ca7cb51cda9a40c2" | ||||||
|     private val language = "de" |     private val language = "de" | ||||||
|     private val preparedParamters = "?api_key=$apiKey&language=$language" |     private val preparedParameters = "?api_key=$apiKey&language=$language" | ||||||
|  |  | ||||||
|     private val imageUrl = "https://image.tmdb.org/t/p/w500" |     private val imageUrl = "https://image.tmdb.org/t/p/w500" | ||||||
|  |  | ||||||
|     fun search(title: String, type: MediaType): TMDBResponse { |     fun search(title: String, type: MediaType): TMDBResponse { | ||||||
|  |         val searchTerm = title.replace("(Sub)", "").trim() | ||||||
|  |  | ||||||
|         return when (type) { |         return when (type) { | ||||||
|             MediaType.MOVIE -> { |             MediaType.MOVIE -> searchMovie(searchTerm) | ||||||
|                 val test = searchMovie(title) |             MediaType.TVSHOW -> searchTVShow(searchTerm) | ||||||
|                 println("test: $test") |             else -> { | ||||||
|                 test |                 Log.e(javaClass.name, "Wrong Type: $type") | ||||||
|             } |  | ||||||
|             MediaType.TVSHOW -> { |  | ||||||
|                 val test = searchTVShow(title) |  | ||||||
|                 println("test: $test") |  | ||||||
|                 test |  | ||||||
|             } |  | ||||||
|             MediaType.OTHER -> { |  | ||||||
|                 Log.e(javaClass.name, "Error") |  | ||||||
|                 TMDBResponse() |                 TMDBResponse() | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -41,19 +37,20 @@ class TMDBApiController { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun searchTVShow(title: String) = runBlocking { |     fun searchTVShow(title: String) = runBlocking { | ||||||
|         val url = URL("$searchTVUrl$preparedParamters&query=${URLEncoder.encode(title, "UTF-8")}") |         val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}") | ||||||
|  |  | ||||||
|         GlobalScope.async { |         GlobalScope.async { | ||||||
|             val response = JsonParser.parseString(url.readText()).asJsonObject |             val response = JsonParser.parseString(url.readText()).asJsonObject | ||||||
|             println(response) |             //println(response) | ||||||
|  |  | ||||||
|             return@async if (response.get("total_results").asInt > 0) { |             return@async if (response.get("total_results").asInt > 0) { | ||||||
|                 response.get("results").asJsonArray.first().let { |                 response.get("results").asJsonArray.first().asJsonObject.let { | ||||||
|                     val overview = it.asJsonObject.get("overview").asString |                     val id = getStringNotNull(it,"id").toInt() | ||||||
|                     val posterPath = imageUrl + it.asJsonObject.get("poster_path").asString |                     val overview = getStringNotNull(it,"overview") | ||||||
|                     val backdropPath = imageUrl + it.asJsonObject.get("backdrop_path").asString |                     val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl) | ||||||
|  |                     val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl) | ||||||
|  |  | ||||||
|                     TMDBResponse("", overview, posterPath, backdropPath) |                     TMDBResponse(id, "", overview, posterPath, backdropPath) | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 TMDBResponse() |                 TMDBResponse() | ||||||
| @ -63,19 +60,21 @@ class TMDBApiController { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun searchMovie(title: String) = runBlocking { |     fun searchMovie(title: String) = runBlocking { | ||||||
|         val url = URL("$searchMovieUrl$preparedParamters&query=${URLEncoder.encode(title, "UTF-8")}") |         val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}") | ||||||
|  |  | ||||||
|         GlobalScope.async { |         GlobalScope.async { | ||||||
|             val response = JsonParser.parseString(url.readText()).asJsonObject |             val response = JsonParser.parseString(url.readText()).asJsonObject | ||||||
|             println(response) |             //println(response) | ||||||
|  |  | ||||||
|             return@async if (response.get("total_results").asInt > 0) { |             return@async if (response.get("total_results").asInt > 0) { | ||||||
|                 response.get("results").asJsonArray.first().let { |                 response.get("results").asJsonArray.first().asJsonObject.let { | ||||||
|                     val overview = it.asJsonObject.get("overview").asString |                     val id = getStringNotNull(it,"id").toInt() | ||||||
|                     val posterPath = imageUrl + it.asJsonObject.get("poster_path").asString |                     val overview = getStringNotNull(it,"overview") | ||||||
|                     val backdropPath = imageUrl + it.asJsonObject.get("backdrop_path").asString |                     val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl) | ||||||
|  |                     val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl) | ||||||
|  |                     val runtime = getMovieRuntime(id) | ||||||
|  |  | ||||||
|                     TMDBResponse("", overview, posterPath, backdropPath) |                     TMDBResponse(id, "", overview, posterPath, backdropPath, runtime) | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 TMDBResponse() |                 TMDBResponse() | ||||||
| @ -85,4 +84,42 @@ class TMDBApiController { | |||||||
|         }.await() |         }.await() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * currently only used for runtime, need a rework | ||||||
|  |      */ | ||||||
|  |     fun getMovieRuntime(id: Int): Int = runBlocking { | ||||||
|  |         val url = URL("$getMovieUrl/$id?api_key=$apiKey&language=$language") | ||||||
|  |  | ||||||
|  |         GlobalScope.async { | ||||||
|  |             val response = JsonParser.parseString(url.readText()).asJsonObject | ||||||
|  |             //println(response) | ||||||
|  |  | ||||||
|  |             val runtime = getStringNotNull(response,"runtime").toInt() | ||||||
|  |             println(runtime) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             return@async runtime | ||||||
|  |         }.await() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * return memberName as string if it's not JsonNull, | ||||||
|  |      * else return an empty string | ||||||
|  |      */ | ||||||
|  |     private fun getStringNotNull(jsonObject: JsonObject, memberName: String): String { | ||||||
|  |         return getStringNotNullPrefix(jsonObject, memberName, "") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * return memberName as string with a prefix if it's not JsonNull, | ||||||
|  |      * else return an empty string | ||||||
|  |      */ | ||||||
|  |     private fun getStringNotNullPrefix(jsonObject: JsonObject, memberName: String, prefix: String): String { | ||||||
|  |         return if (!jsonObject.get(memberName).isJsonNull) { | ||||||
|  |             prefix + jsonObject.get(memberName).asString | ||||||
|  |         } else { | ||||||
|  |             "" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
							
								
								
									
										10
									
								
								app/src/main/res/drawable/ic_baseline_check_circle_24.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/drawable/ic_baseline_check_circle_24.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="24dp" | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportWidth="24" | ||||||
|  |     android:viewportHeight="24" | ||||||
|  |     android:tint="?attr/colorControlNormal"> | ||||||
|  |   <path | ||||||
|  |       android:fillColor="@android:color/white" | ||||||
|  |       android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/> | ||||||
|  | </vector> | ||||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/shape_rounden_corner.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/drawable/shape_rounden_corner.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <solid android:color="#B0B0B0"/> | ||||||
|  |     <corners android:radius="3dp"/> | ||||||
|  | </shape> | ||||||
| @ -17,7 +17,7 @@ | |||||||
|         app:layout_constraintRight_toRightOf="parent" |         app:layout_constraintRight_toRightOf="parent" | ||||||
|         app:menu="@menu/bottom_nav_menu" /> |         app:menu="@menu/bottom_nav_menu" /> | ||||||
|  |  | ||||||
|     <fragment |     <androidx.fragment.app.FragmentContainerView | ||||||
|         android:id="@+id/nav_host_fragment" |         android:id="@+id/nav_host_fragment" | ||||||
|         android:name="androidx.navigation.fragment.NavHostFragment" |         android:name="androidx.navigation.fragment.NavHostFragment" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|  | |||||||
| @ -16,24 +16,33 @@ | |||||||
|  |  | ||||||
|         <ImageView |         <ImageView | ||||||
|             android:id="@+id/image_episode" |             android:id="@+id/image_episode" | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="128dp" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="72dp" | ||||||
|             android:layout_weight="1" |             android:contentDescription="@string/component_poster_desc" | ||||||
|             android:minWidth="48dp" |  | ||||||
|             app:srcCompat="@drawable/ic_baseline_account_box_24" /> |             app:srcCompat="@drawable/ic_baseline_account_box_24" /> | ||||||
|  |  | ||||||
|         <TextView |         <TextView | ||||||
|             android:id="@+id/text_episode_title" |             android:id="@+id/text_episode_title" | ||||||
|             android:layout_width="match_parent" |             android:layout_width="0dp" | ||||||
|             android:layout_height="match_parent" |             android:layout_height="match_parent" | ||||||
|             android:layout_marginStart="7dp" |             android:layout_marginStart="7dp" | ||||||
|             android:layout_weight="1" |             android:layout_weight="1" | ||||||
|             android:text="TextView" |             android:text="@string/component_episode_title" | ||||||
|             android:textSize="16sp" /> |             android:textSize="16sp" /> | ||||||
|  |  | ||||||
|  |         <ImageView | ||||||
|  |             android:id="@+id/image_watched" | ||||||
|  |             android:layout_width="30dp" | ||||||
|  |             android:layout_height="30dp" | ||||||
|  |             android:layout_margin="2dp" | ||||||
|  |             android:contentDescription="@string/component_watched_desc" | ||||||
|  |             app:srcCompat="@drawable/ic_baseline_check_circle_24" /> | ||||||
|     </LinearLayout> |     </LinearLayout> | ||||||
|  |  | ||||||
|     <TextView |     <TextView | ||||||
|         android:id="@+id/text_episode_desc" |         android:id="@+id/text_episode_desc" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="wrap_content" /> |         android:layout_height="wrap_content" | ||||||
|  |         android:maxLines="2" | ||||||
|  |         android:ellipsize="end"/> | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
| @ -39,6 +39,7 @@ | |||||||
|                     android:id="@+id/linear_account_login" |                     android:id="@+id/linear_account_login" | ||||||
|                     android:layout_width="match_parent" |                     android:layout_width="match_parent" | ||||||
|                     android:layout_height="match_parent" |                     android:layout_height="match_parent" | ||||||
|  |                     android:layout_margin="7dp" | ||||||
|                     android:gravity="center" |                     android:gravity="center" | ||||||
|                     android:orientation="horizontal"> |                     android:orientation="horizontal"> | ||||||
|  |  | ||||||
| @ -103,6 +104,7 @@ | |||||||
|                     android:id="@+id/linear_about" |                     android:id="@+id/linear_about" | ||||||
|                     android:layout_width="match_parent" |                     android:layout_width="match_parent" | ||||||
|                     android:layout_height="match_parent" |                     android:layout_height="match_parent" | ||||||
|  |                     android:layout_margin="7dp" | ||||||
|                     android:gravity="center" |                     android:gravity="center" | ||||||
|                     android:orientation="horizontal"> |                     android:orientation="horizontal"> | ||||||
|  |  | ||||||
| @ -110,6 +112,7 @@ | |||||||
|                         android:id="@+id/imageView2" |                         android:id="@+id/imageView2" | ||||||
|                         android:layout_width="wrap_content" |                         android:layout_width="wrap_content" | ||||||
|                         android:layout_height="wrap_content" |                         android:layout_height="wrap_content" | ||||||
|  |                         android:contentDescription="@string/info" | ||||||
|                         android:minWidth="48dp" |                         android:minWidth="48dp" | ||||||
|                         android:minHeight="48dp" |                         android:minHeight="48dp" | ||||||
|                         android:padding="5dp" |                         android:padding="5dp" | ||||||
| @ -140,6 +143,23 @@ | |||||||
|                     </LinearLayout> |                     </LinearLayout> | ||||||
|  |  | ||||||
|                 </LinearLayout> |                 </LinearLayout> | ||||||
|  |  | ||||||
|  |                 <View | ||||||
|  |                     android:id="@+id/divider" | ||||||
|  |                     android:layout_width="match_parent" | ||||||
|  |                     android:layout_height="1dp" | ||||||
|  |                     android:background="?android:attr/listDivider" /> | ||||||
|  |  | ||||||
|  |                 <TextView | ||||||
|  |                     android:id="@+id/text_licenses" | ||||||
|  |                     android:layout_width="match_parent" | ||||||
|  |                     android:layout_height="wrap_content" | ||||||
|  |                     android:layout_margin="7dp" | ||||||
|  |                     android:paddingStart="48dp" | ||||||
|  |                     android:paddingEnd="48dp" | ||||||
|  |                     android:text="Licenses" | ||||||
|  |                     android:textColor="@android:color/primary_text_light" | ||||||
|  |                     android:textSize="16sp" /> | ||||||
|             </LinearLayout> |             </LinearLayout> | ||||||
|         </LinearLayout> |         </LinearLayout> | ||||||
|     </ScrollView> |     </ScrollView> | ||||||
|  | |||||||
| @ -17,55 +17,101 @@ | |||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:orientation="vertical"> |             android:orientation="vertical"> | ||||||
|  |  | ||||||
|  |             <FrameLayout | ||||||
|  |                 android:layout_width="match_parent" | ||||||
|  |                 android:layout_height="wrap_content"> | ||||||
|  |  | ||||||
|  |                 <ImageView | ||||||
|  |                     android:id="@+id/image_backdrop" | ||||||
|  |                     android:layout_width="match_parent" | ||||||
|  |                     android:layout_height="wrap_content" | ||||||
|  |                     android:adjustViewBounds="false" | ||||||
|  |                     android:maxHeight="231dp" | ||||||
|  |                     android:minHeight="220dp" | ||||||
|  |                     android:scaleType="centerCrop" /> | ||||||
|  |  | ||||||
|                 <ImageView |                 <ImageView | ||||||
|                     android:id="@+id/image_poster" |                     android:id="@+id/image_poster" | ||||||
|                     android:layout_width="wrap_content" |                     android:layout_width="wrap_content" | ||||||
|                     android:layout_height="wrap_content" |                     android:layout_height="wrap_content" | ||||||
|                     android:layout_gravity="center" |                     android:layout_gravity="center" | ||||||
|                 android:layout_marginTop="20dp" |  | ||||||
|                     android:minHeight="200dp" |                     android:minHeight="200dp" | ||||||
|                     android:src="@drawable/ic_launcher_background" /> |                     android:src="@drawable/ic_launcher_background" /> | ||||||
|  |  | ||||||
|             <Button |             </FrameLayout> | ||||||
|  |  | ||||||
|  |             <LinearLayout | ||||||
|  |                 android:id="@+id/linear_media_info" | ||||||
|  |                 android:layout_width="match_parent" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:layout_marginTop="10dp" | ||||||
|  |                 android:gravity="center" | ||||||
|  |                 android:orientation="horizontal"> | ||||||
|  |  | ||||||
|  |                 <TextView | ||||||
|  |                     android:id="@+id/text_year" | ||||||
|  |                     android:layout_width="wrap_content" | ||||||
|  |                     android:layout_height="wrap_content" | ||||||
|  |                     android:padding="2dp" | ||||||
|  |                     android:text="@string/text_year_ex" /> | ||||||
|  |  | ||||||
|  |                 <TextView | ||||||
|  |                     android:id="@+id/text_age" | ||||||
|  |                     android:layout_width="wrap_content" | ||||||
|  |                     android:layout_height="wrap_content" | ||||||
|  |                     android:layout_marginStart="7dp" | ||||||
|  |                     android:background="@drawable/shape_rounden_corner" | ||||||
|  |                     android:paddingStart="3dp" | ||||||
|  |                     android:paddingTop="2dp" | ||||||
|  |                     android:paddingEnd="3dp" | ||||||
|  |                     android:paddingBottom="2dp" | ||||||
|  |                     android:text="@string/text_age_ex" /> | ||||||
|  |  | ||||||
|  |                 <TextView | ||||||
|  |                     android:id="@+id/text_episodes_or_runtime" | ||||||
|  |                     android:layout_width="wrap_content" | ||||||
|  |                     android:layout_height="wrap_content" | ||||||
|  |                     android:layout_marginStart="7dp" | ||||||
|  |                     android:padding="2dp" | ||||||
|  |                     android:text="@string/text_episodes_count" /> | ||||||
|  |             </LinearLayout> | ||||||
|  |  | ||||||
|  |             <com.google.android.material.button.MaterialButton | ||||||
|                 android:id="@+id/button_play" |                 android:id="@+id/button_play" | ||||||
|                 android:layout_width="match_parent" |                 android:layout_width="match_parent" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:layout_gravity="center" |  | ||||||
|                 android:layout_marginStart="7dp" |                 android:layout_marginStart="7dp" | ||||||
|                 android:layout_marginTop="24dp" |                 android:layout_marginTop="6dp" | ||||||
|                 android:layout_marginEnd="7dp" |                 android:layout_marginEnd="7dp" | ||||||
|                 android:background="#4A4141" |                 android:gravity="center" | ||||||
|                 android:drawableStart="@drawable/ic_baseline_play_arrow_24" |  | ||||||
|                 android:drawablePadding="10dp" |  | ||||||
|                 android:drawableTint="#FFFFFF" |  | ||||||
|                 android:gravity="start|center_vertical" |  | ||||||
|                 android:paddingStart="160dp" |  | ||||||
|                 android:paddingEnd="160dp" |  | ||||||
|                 android:text="@string/button_play" |                 android:text="@string/button_play" | ||||||
|                 android:textAllCaps="false" |                 android:textAllCaps="false" | ||||||
|                 android:textColor="@android:color/primary_text_dark" |                 android:textColor="@android:color/primary_text_dark" | ||||||
|                 android:textSize="16sp" /> |                 android:textSize="16sp" | ||||||
|  |                 app:backgroundTint="#4A4141" | ||||||
|  |                 app:icon="@drawable/ic_baseline_play_arrow_24" | ||||||
|  |                 app:iconGravity="textStart" /> | ||||||
|  |  | ||||||
|             <TextView |             <TextView | ||||||
|                 android:id="@+id/text_title" |                 android:id="@+id/text_title" | ||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
|                 android:layout_height="19dp" |                 android:layout_height="wrap_content" | ||||||
|                 android:layout_gravity="center" |                 android:layout_gravity="center" | ||||||
|                 android:layout_marginStart="7dp" |                 android:layout_marginStart="7dp" | ||||||
|                 android:layout_marginTop="12dp" |                 android:layout_marginTop="12dp" | ||||||
|                 android:layout_marginEnd="7dp" |                 android:layout_marginEnd="7dp" | ||||||
|                 android:text="TextView" |                 android:text="@string/text_title_ex" | ||||||
|                 android:textStyle="bold" /> |                 android:textStyle="bold" /> | ||||||
|  |  | ||||||
|             <TextView |             <TextView | ||||||
|                 android:id="@+id/text_desc" |                 android:id="@+id/text_overview" | ||||||
|                 android:layout_width="match_parent" |                 android:layout_width="match_parent" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:layout_gravity="center" |                 android:layout_gravity="center" | ||||||
|                 android:layout_marginStart="7dp" |                 android:layout_marginStart="12dp" | ||||||
|                 android:layout_marginTop="10dp" |                 android:layout_marginTop="7dp" | ||||||
|                 android:layout_marginEnd="7dp" |                 android:layout_marginEnd="12dp" | ||||||
|                 android:text="TextView" /> |                 android:text="@string/text_overview_ex" /> | ||||||
|  |  | ||||||
|             <androidx.recyclerview.widget.RecyclerView |             <androidx.recyclerview.widget.RecyclerView | ||||||
|                 android:id="@+id/recycler_episodes" |                 android:id="@+id/recycler_episodes" | ||||||
|  | |||||||
							
								
								
									
										63
									
								
								app/src/main/res/raw/notices.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/src/main/res/raw/notices.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <notices> | ||||||
|  |     <notice> | ||||||
|  |         <name>AndroidX</name> | ||||||
|  |         <url>https://developer.android.com/jetpack/androidx</url> | ||||||
|  |         <copyright>Copyright The Android Open Source Project</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>Material Components for Android</name> | ||||||
|  |         <url>https://github.com/material-components/material-components-android</url> | ||||||
|  |         <copyright>Copyright The Android Open Source Project</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>ExoPlayer</name> | ||||||
|  |         <url>https://github.com/material-components/material-components-android</url> | ||||||
|  |         <copyright>Copyright The Android Open Source Project</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>Gson</name> | ||||||
|  |         <url>https://github.com/google/gson</url> | ||||||
|  |         <copyright>Copyright 2008 Google Inc.</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>Material design icons</name> | ||||||
|  |         <url>https://github.com/google/material-design-icons</url> | ||||||
|  |         <copyright>Copyright Google Inc.</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>Material Dialogs</name> | ||||||
|  |         <url>https://github.com/afollestad/material-dialogs</url> | ||||||
|  |         <copyright>Copyright Aidan Follestad</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>Jsoup</name> | ||||||
|  |         <url>https://jsoup.org/</url> | ||||||
|  |         <copyright>Copyright 2009 - 2020 Jonathan Hedley</copyright> | ||||||
|  |         <license>MIT License</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>kotlinx.coroutines</name> | ||||||
|  |         <url>https://github.com/Kotlin/kotlinx.coroutines</url> | ||||||
|  |         <copyright>Copyright 2016 - 2019 JetBrains</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>Glide</name> | ||||||
|  |         <url>https://github.com/bumptech/glide</url> | ||||||
|  |         <copyright>Copyright Google, Inc</copyright> | ||||||
|  |         <license>BSD 3-Clause License</license> | ||||||
|  |     </notice> | ||||||
|  |     <notice> | ||||||
|  |         <name>Glide Transformations</name> | ||||||
|  |         <url>https://github.com/wasabeef/glide-transformations</url> | ||||||
|  |         <copyright>Copyright 2020 Wasabeef</copyright> | ||||||
|  |         <license>Apache Software License 2.0</license> | ||||||
|  |     </notice> | ||||||
|  | </notices> | ||||||
							
								
								
									
										34
									
								
								app/src/main/res/values-de-rDE/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/src/main/res/values-de-rDE/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <resources> | ||||||
|  |     <string name="title_home">Startseite</string> | ||||||
|  |     <string name="title_library">Übersicht</string> | ||||||
|  |     <string name="title_search">Suche</string> | ||||||
|  |     <string name="title_account">Account</string> | ||||||
|  |  | ||||||
|  |     <!-- search fragment --> | ||||||
|  |     <string name="search_hint">Suche nach Filmen und Serien</string> | ||||||
|  |  | ||||||
|  |     <!-- media fragment --> | ||||||
|  |     <string name="button_play">Abspielen</string> | ||||||
|  |     <string name="text_episodes_count">%1$d Episoden</string> | ||||||
|  |     <string name="text_runtime">%1$d Minuten</string> | ||||||
|  |     <string name="component_episode_title">Episode %1$d %2$s</string> | ||||||
|  |  | ||||||
|  |     <!-- settings fragment --> | ||||||
|  |     <string name="account">Account</string> | ||||||
|  |     <string name="account_login_desc">Zum bearbeiten tippen</string> | ||||||
|  |     <string name="info">Info</string> | ||||||
|  |     <string name="info_about_desc">Version %1$s (%2$s)</string> | ||||||
|  |     <string name="info_about_dialog">Diese App wird unter den Bedingungen der GNU GPL 3 oder höher zur Verfügung gestellt. Weiter Informationen findest du unter: \ngit.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string> | ||||||
|  |     <string name="licenses">Lizenzen</string> | ||||||
|  |  | ||||||
|  |     <!-- dialogs --> | ||||||
|  |     <string name="save">speichern</string> | ||||||
|  |     <string name="cancel">\@android:string/cancel</string> | ||||||
|  |  | ||||||
|  |     <!-- etc --> | ||||||
|  |     <string name="login">Login</string> | ||||||
|  |     <string name="login_desc">Um Teapod zu benutzen musst du eingeloggt sein. Die Login-Daten weden verschüsselt aud deinem Gerät gespeichert.</string> | ||||||
|  |     <string name="login_failed_desc">Login nicht erfolgreich. Bitte versuche es erneut.</string> | ||||||
|  |     <string name="password">Passwort</string> | ||||||
|  | </resources> | ||||||
| @ -1,5 +1,5 @@ | |||||||
| <resources> | <resources> | ||||||
|     <string name="app_name">Teapod</string> |     <string name="app_name" translatable="false">Teapod</string> | ||||||
|     <string name="title_home">Home</string> |     <string name="title_home">Home</string> | ||||||
|     <string name="title_library">Library</string> |     <string name="title_library">Library</string> | ||||||
|     <string name="title_search">Search</string> |     <string name="title_search">Search</string> | ||||||
| @ -10,15 +10,25 @@ | |||||||
|  |  | ||||||
|     <!-- media fragment --> |     <!-- media fragment --> | ||||||
|     <string name="button_play">Play</string> |     <string name="button_play">Play</string> | ||||||
|  |     <string name="text_title_ex" translatable="false">A Silent Voice</string> | ||||||
|  |     <string name="text_overview_ex" translatable="false">Shouya Ishida starts bullying the new girl in class …</string> | ||||||
|  |     <string name="text_year_ex" translatable="false">2016</string> | ||||||
|  |     <string name="text_age_ex" translatable="false">6</string> | ||||||
|  |     <string name="text_episodes_count">%1$d episodes</string> | ||||||
|  |     <string name="text_runtime">%1$d Minutes</string> | ||||||
|  |     <string name="component_episode_title">Episode %1$d %2$s</string> | ||||||
|  |     <string name="component_poster_desc" translatable="false">episode poster</string> | ||||||
|  |     <string name="component_watched_desc" translatable="false">already watched</string> | ||||||
|  |  | ||||||
|     <!-- settings fragment --> |     <!-- settings fragment --> | ||||||
|     <string name="account">Account</string> |     <string name="account">Account</string> | ||||||
|     <string name="account_login_ex">user@example.com</string> |     <string name="account_login_ex" translatable="false">user@example.com</string> | ||||||
|     <string name="account_login_desc">Tap to edit</string> |     <string name="account_login_desc">Tap to edit</string> | ||||||
|     <string name="info">Info</string> |     <string name="info">Info</string> | ||||||
|     <string name="info_about">Teapod by @Seil0</string> |     <string name="info_about" translatable="false">Teapod by @Seil0</string> | ||||||
|     <string name="info_about_desc" translatable="false">Version %1$s (%2$s)</string> |     <string name="info_about_desc">Version %1$s (%2$s)</string> | ||||||
|     <string name="info_about_dialog" translatable="false">This software is published under the terms and conditions of GPL 3. For further information visit git.mosad.xyz/Seil0 \n\n© 2020 seil0@mosad.xyz</string> |     <string name="info_about_dialog">This app is published under the terms and conditions of the GNU GPL 3 or later. For further information visit: \ngit.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string> | ||||||
|  |     <string name="licenses">Licenses</string> | ||||||
|  |  | ||||||
|     <!-- dialogs --> |     <!-- dialogs --> | ||||||
|     <string name="save">save</string> |     <string name="save">save</string> | ||||||
| @ -26,7 +36,8 @@ | |||||||
|  |  | ||||||
|     <!-- etc --> |     <!-- etc --> | ||||||
|     <string name="login">Login</string> |     <string name="login">Login</string> | ||||||
|     <string name="login_desc">You need to login before you can use Teapod. Your Login-Data will be stored encrypted on your device.</string> |     <string name="login_desc">You need to login before you can use Teapod. The Login-Data will be stored encrypted on your device.</string> | ||||||
|  |     <string name="login_failed_desc">Could not login. Please try again.</string> | ||||||
|     <string name="password">Password</string> |     <string name="password">Password</string> | ||||||
|  |  | ||||||
|     <!-- save keys --> |     <!-- save keys --> | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| <resources> | <resources> | ||||||
|     <!-- Base application theme. --> |     <!-- Base application theme. --> | ||||||
|     <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> |     <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> | ||||||
|         <!-- Customize your theme here. --> |         <!-- Customize your theme here. --> | ||||||
|         <item name="colorPrimary">@color/colorPrimary</item> |         <item name="colorPrimary">@color/colorPrimary</item> | ||||||
|         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> |         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||||
|         <item name="colorAccent">@color/colorAccent</item> |         <item name="colorAccent">@color/colorAccent</item> | ||||||
|     </style> |     </style> | ||||||
|  |  | ||||||
|     <style name="AppTheme.AppCompat.Light.NoActionBar.FullScreen" parent="@style/Theme.AppCompat.Light.NoActionBar"> |     <style name="AppTheme.MaterialComponents.Light.NoActionBar.FullScreen" parent="@style/Theme.MaterialComponents.Light.NoActionBar"> | ||||||
|         <item name="android:windowNoTitle">true</item> |         <item name="android:windowNoTitle">true</item> | ||||||
|         <item name="android:windowActionBar">false</item> |         <item name="android:windowActionBar">false</item> | ||||||
|         <item name="android:windowFullscreen">true</item> |         <item name="android:windowFullscreen">true</item> | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ buildscript { | |||||||
|         jcenter() |         jcenter() | ||||||
|     } |     } | ||||||
|     dependencies { |     dependencies { | ||||||
|         classpath 'com.android.tools.build:gradle:4.0.2' |         classpath 'com.android.tools.build:gradle:4.1.0' | ||||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" |         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||||
|  |  | ||||||
|         // NOTE: Do not place your application dependencies here; they belong |         // NOTE: Do not place your application dependencies here; they belong | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| #Thu Oct 08 16:06:13 CEST 2020 | #Tue Oct 13 12:04:29 CEST 2020 | ||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user