commit c5427d1a1fa9a2a2574a05cd48b05ceadbdd86f6 Author: Kevin Puertas Date: Sat Dec 2 21:31:49 2017 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2481896 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +## Android Studio 3 gitignore file +## It ignores also config files for making full portable into different computers +#built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Windows thumbnail db +Thumbs.db + +# OSX files +.DS_Store + +# Android Studio +*.iml +.idea +#.idea/workspace.xml - remove # and delete .idea if it better suit your needs. +.gradle +build/ +.navigation +captures/ +output.json #Since Android Studio 3.0 + +#NDK +obj/ +.externalNativeBuild + +#Admob strings +app/src/main/res/values/admob.xml \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..af4048b --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Codigo fuente de "Frases de Anime" + +Frases de Anime es una aplicacion para Android en la que todos los días tendrás una nueva frase de una serie Anime o película en tus notificaciones + +Podrás ver frases anteriores, marcar como favoritos, o compartirlas con tus amigos + +## Requisitos para compilarla + +Android Studio 3.0+ + +Kotlin + +Anko (Debería descargarse automáticamente) + +## Problemas al compilar la aplicación + +La aplicación puede dar problemas de compilación al faltar las claves API de Admob (No subidas porque son propias, evitamos un mal uso de la API). En res/strings.xml hay unas de ejemplo, deberían valer + +## Licencia + +Mire el fichero LICENSE.md para detalles, usamos licencia GPL \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..e3626d7 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,58 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' // apply kotlin android plugin +apply plugin: 'kotlin-android-extensions' + +def anko_version = '0.10.1' +android { + compileSdkVersion 26 + buildToolsVersion '26.0.2' + defaultConfig { + applicationId "com.jkanetwork.st.com.jkanetwork.st.frasesdeanime" + minSdkVersion 17 + targetSdkVersion 26 + // Sube automáticamente la build en cada compilacion + def versionPropsFile = file('versioncode.count') + Properties versionProps = new Properties() + versionProps.load(new FileInputStream(versionPropsFile)) + def code = versionProps['VERSION_CODE'].toInteger() + 1 + versionProps['VERSION_CODE'] = code.toString() + versionProps.store(versionPropsFile.newWriter(), null) + // Fin de subir automáticamente la build en cada compilación + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } +} + +sourceSets { + main.java.srcDirs += 'src/main/kotlin' +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:26.1.0' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile "org.jetbrains.anko:anko-sqlite:$anko_version" + compile "org.jetbrains.anko:anko-commons:$anko_version" + // Anko Layouts + compile "org.jetbrains.anko:anko-sdk25:$anko_version" + // sdk15, sdk19, sdk21, sdk23 are also available + compile "org.jetbrains.anko:anko-appcompat-v7:$anko_version" + // Coroutine listeners for Anko Layouts + compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version" + compile "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version" + compile 'com.github.mirrajabi:kotlin-preferences-extensions:1.0' + compile 'com.android.support:recyclerview-v7:26.1.0' + compile 'com.google.android.gms:play-services-ads:11.6.2' + testCompile 'junit:junit:4.12' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..daf2ee3 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/kprkpr/AndroidStudio/sdkLinux/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/release/output.json b/app/release/output.json new file mode 100644 index 0000000..a55f841 --- /dev/null +++ b/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":-1},"path":"app-release.apk","properties":{"packageId":"com.jkanetwork.st.com.jkanetwork.st.frasesdeanime","split":"","minSdkVersion":"17"}}] \ No newline at end of file diff --git a/app/src/androidTest/java/com/jkanetwork/st/frasesdeanime/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/jkanetwork/st/frasesdeanime/ExampleInstrumentedTest.java new file mode 100644 index 0000000..c20c8dc --- /dev/null +++ b/app/src/androidTest/java/com/jkanetwork/st/frasesdeanime/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.jkanetwork.st.frasesdeanime; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.jkanetwork.st.com.jkanetwork.st.frasesdeanime", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..616150c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AboutActivity.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AboutActivity.kt new file mode 100644 index 0000000..f152506 --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AboutActivity.kt @@ -0,0 +1,20 @@ +package com.jkanetwork.st.frasesdeanime + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.content.Context + +/** + * Created by kprkpr on 30/08/17. + */ +class AboutActivity : AppCompatActivity(){ + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_about) + } + + /*override fun onBackPressed() { + startActivity() + }*/ +} diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AlarmNotif.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AlarmNotif.kt new file mode 100644 index 0000000..ef7242d --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AlarmNotif.kt @@ -0,0 +1,58 @@ +package com.jkanetwork.st.frasesdeanime + +import android.app.Notification +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import java.util.Calendar +import android.app.AlarmManager +import android.support.v4.app.NotificationCompat +import android.app.PendingIntent + +/* + Help of: https://github.com/okwrtdsh/AlarmTest/blob/master/app/src/main/kotlin/com/github/okwrtdsh/alarmtest/AlarmBroadcastReceiver.kt + + */ + +class AlarmNotif : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent?) { + var db = SQLiteHelper(context) + var P = Prefs(context) + val time = Calendar.getInstance() + var frase = db.getSentenceFromIDF(db.getIDFraseHoy()) + var anime = db.getAnimeFromIDF(db.getIDFraseHoy()) + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + + nm.cancel(1); + val notificationBuilder = NotificationCompat.Builder(context, "STN") + .setContentTitle(anime) + .setStyle(NotificationCompat.BigTextStyle().bigText(frase)) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentText(frase) + .setWhen(System.currentTimeMillis()) + .setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0)) + + var noti = notificationBuilder.build(); + + if (P.getPersNotifOn() == true) { + noti.flags = Notification.FLAG_ONGOING_EVENT + } + + nm.notify(1,noti); + + if (P.getPersNotifOn() == true) { + /* Do notif change all days */ + time.add(Calendar.DATE, 1) + time.set(Calendar.HOUR_OF_DAY, 0) + time.set(Calendar.MINUTE, 0) + time.set(Calendar.SECOND, 1) + val sender = PendingIntent.getBroadcast(context, 1, Intent(context, AlarmNotif::class.java), 0) + alarmMgr.setRepeating(AlarmManager.RTC, time.getTimeInMillis(), AlarmManager.INTERVAL_DAY, sender) // Repeat every day + }else{ /* For boot actions */ + putAlarm(context) + } + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AnimesSelActivity.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AnimesSelActivity.kt new file mode 100644 index 0000000..498cd07 --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/AnimesSelActivity.kt @@ -0,0 +1,104 @@ +package com.jkanetwork.st.frasesdeanime + +import android.content.Context +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.widget.SimpleAdapter +import android.widget.ListView +import android.widget.TextView +import android.widget.Button +import org.jetbrains.anko.* +import org.jetbrains.anko.support.v4.UI +import org.jetbrains.anko.support.v4.find +import android.widget.Toast +import android.util.Log + + +import android.support.v4.content.ContextCompat.startActivity +import android.content.Intent +import android.widget.AdapterView + + +/** + * Created by kprkpr on 25/07/17. + * Help: https://www.youtube.com/watch?v=x-M1L6tcsLo + */ +class AnimesSelActivity : AppCompatActivity(){ + val act = this /* Activity Context */ + + /* For DSL bind */ + lateinit var view_list : ListView + lateinit var btn_exit : Button + lateinit var btn_sel : Button + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + /** Layout in DSL */ + verticalLayout { + linearLayout { + btn_exit = button("Volver"); + btn_sel = button("Seleccionar todos").lparams(width = matchParent); + } + view_list = listView { + }.lparams(width = matchParent, height = matchParent) { + } + } + + + val data = ArrayList>() + var db = SQLiteHelper(applicationContext) + val listIDs = db.getAllAnimeIDs() + var listNames = db.getAllAnimeNames() + val length = listIDs.size + + + if (db.getAllAnimeSel()){ //If all selected + btn_sel.setText("Deseleccionar todos") + }else{ + btn_sel.setText("Seleccionar todos") + } + + for (i in 0..length -1){ + val item = HashMap() + item.put("anime",listNames[i]) + if (db.getAnimeSel(listIDs[i]) == true) { + item.put("selected", "Seleccionado") + }else{ + item.put("selected", "No seleccionado") + } + data.add(item) + } + + var adapter = SimpleAdapter(this,data,android.R.layout.simple_list_item_2, + arrayOf("anime","selected"), intArrayOf(android.R.id.text1,android.R.id.text2)) + + view_list.adapter = adapter + + /* For selected item, change status */ + view_list.setOnItemClickListener(object : AdapterView.OnItemClickListener{ + override fun onItemClick (parent: AdapterView<*>, view: View, pos: Int, id: Long){ + db.setAnimeSel(listIDs[pos]) + restartActivity(act) + } + }) + + btn_sel.setOnClickListener() { + db.setAllAnimeSel() + restartActivity(act) + } + + //"Return" to main + btn_exit.setOnClickListener() { + startActivity() + } + + } + + /** On back go to main */ + override fun onBackPressed() { + startActivity() + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/App.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/App.kt new file mode 100644 index 0000000..cd40a92 --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/App.kt @@ -0,0 +1,83 @@ +package com.jkanetwork.st.frasesdeanime + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.content.Intent +import android.support.v7.app.AppCompatActivity +import android.app.PendingIntent +import java.util.Calendar +import android.app.AlarmManager +import android.util.Log +import android.app.Notification +import android.app.NotificationManager + +/** + * Created by kprkpr on 18/07/17. + * Here, functions for using in every part of app + */ + + +/** Restart activity without doing animations for refresh favs + * @param act: Use "this.intent" + * */ +fun restartActivity(act: AppCompatActivity){ + val intent = act.intent + act.overridePendingTransition(0, 0) + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) + act.finish() + act.overridePendingTransition(0, 0) + act.startActivity(intent) +} + + +/* Call for putting alarm */ +fun putAlarm(context: Context) { + var P = Prefs(context) + //val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + /* Common vars for alarm config */ + val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val sender = PendingIntent.getBroadcast(context, 1, Intent(context, AlarmNotif::class.java), 0) + + /* Cancel alarm first */ + alarmMgr.cancel(sender) + //nm.cancel(1); + + + var time = Calendar.getInstance() + if (P.getNotifOn()) { + if (P.getPersNotifOn()) { /* Persistent */ + var intent = Intent() + intent.setAction("com.jkanetwork.st.com.jkanetwork.st.frasesdeanime.notif") + context.sendBroadcast(intent) + + }else{ /* At x hour every day */ + + /* Here set the alarm to one time a day in selected date. Default: 8AM */ + /* Shedechule Alarm for tomorrow instead before now if alarm set to before. */ + if (time.get(Calendar.HOUR_OF_DAY) > P.getHour() || ( time.get(Calendar.HOUR_OF_DAY) == P.getHour() && time.get(Calendar.MINUTE) > P.getMinute())) { + /* Add a day */ + time.add(Calendar.DATE, 1) + } + time.set(Calendar.HOUR_OF_DAY, P.getHour()) + time.set(Calendar.MINUTE, P.getMinute()) + time.set(Calendar.SECOND, 0) + Log.d("STLOG (Next)", time.getTime().toString()) + alarmMgr.setRepeating(AlarmManager.RTC, time.getTimeInMillis(), AlarmManager.INTERVAL_DAY, sender) // Repeat every day + } + + + } +} + + +class App : Application() { + + companion object { + + } + + override fun onCreate() { + super.onCreate() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/FavsActivity.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/FavsActivity.kt new file mode 100644 index 0000000..a152e10 --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/FavsActivity.kt @@ -0,0 +1,94 @@ +package com.jkanetwork.st.frasesdeanime + +import android.content.Context +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.content.Intent +import android.view.View +import android.widget.SimpleAdapter +import android.widget.ListView +import android.widget.TextView +import android.view.ContextMenu +import android.view.MenuItem +import android.widget.AdapterView +import android.content.ClipboardManager +import android.content.ClipData +import org.jetbrains.anko.* + +//import kotlinx.android.synthetic.main.activity_simplelistview.* + +/** + * Created by kprkpr on 25/07/17. + * Help: https://www.youtube.com/watch?v=x-M1L6tcsLo + */ +class FavsActivity : AppCompatActivity(){ + + /* For DSL bind */ + lateinit var view_list : ListView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + /** Layout in DSL */ + verticalLayout { + view_list = listView { + }.lparams(width = matchParent, height = matchParent) { + } + } + + val data = ArrayList>() + var db = SQLiteHelper(applicationContext) + val listIDFrases = db.getAllIDFavs() + val length = listIDFrases.size + + for (i in 0..length -1){ + val item = HashMap() + item.put("sentence",db.getSentenceFromIDF(listIDFrases[i])) + item.put("anime",db.getAnimeFromIDF(listIDFrases[i])) + data.add(item) + } + + view_list.adapter = SimpleAdapter(this,data,android.R.layout.simple_list_item_2, + arrayOf("sentence","anime"), intArrayOf(android.R.id.text1,android.R.id.text2)) + + registerForContextMenu(view_list) + + } + + override fun onCreateContextMenu(menu: ContextMenu, v: View, + menuInfo: ContextMenu.ContextMenuInfo) { + super.onCreateContextMenu(menu, v, menuInfo) + val inflater = getMenuInflater() + inflater.inflate(R.menu.menu_favsentence, menu) + } + + + override fun onContextItemSelected(item: MenuItem): Boolean { + val info = item.getMenuInfo() as AdapterView.AdapterContextMenuInfo + val obj = view_list.getItemAtPosition(info.position) as HashMap + + when (item.getItemId()) { + R.id.copy -> { + var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setPrimaryClip(ClipData.newPlainText("Sentence", "${obj.get("sentence")} (${obj.get("anime")}) \n ${this.getString(R.string.sharefrom)}")) + toast("Copiado al portapapeles") + return true + } + R.id.delfav -> { + var db = SQLiteHelper(applicationContext) + val IDFavs = db.getAllIDFavs() + db.setFav(IDFavs[info.position]) + restartActivity(this) + return true + } + R.id.share -> { + share("${obj.get("sentence")} (${obj.get("anime")}) \n ${this.getString(R.string.sharefrom)}") + return true + } + } + return false + } + + + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/MainActivity.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/MainActivity.kt new file mode 100644 index 0000000..c9ccf44 --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/MainActivity.kt @@ -0,0 +1,252 @@ +package com.jkanetwork.st.frasesdeanime + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import org.jetbrains.anko.* +import android.util.Log +import android.content.Context +import android.content.SharedPreferences +import android.view.Menu +import android.view.View +//import android.view.ImageView +import android.view.MenuItem +import java.util.Calendar +import java.text.DateFormatSymbols +import android.support.v4.view.ViewPager +import android.widget.Toast + +import kotlinx.android.synthetic.main.activity_main.* + +/* Ads */ +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.MobileAds; +import com.google.android.gms.ads.reward.RewardItem; +import com.google.android.gms.ads.reward.RewardedVideoAd; +import com.google.android.gms.ads.reward.RewardedVideoAdListener; +import android.text.method.ScrollingMovementMethod + + +class MainActivity : AppCompatActivity(), RewardedVideoAdListener { + + // Overrides for making it compile ok, it needs all methods + override fun onRewardedVideoAdLeftApplication() {} + override fun onRewardedVideoAdClosed() {} + override fun onRewardedVideoAdFailedToLoad(errorCode: Int) {} + override fun onRewardedVideoAdLoaded() {} + override fun onRewardedVideoAdOpened() {} + override fun onRewardedVideoStarted() {} + + + /* Prefs, database init and calendar data */ + lateinit var P: Prefs + lateinit var db: SQLiteHelper + var cal = Calendar.getInstance() + var dayS = cal.get(Calendar.DAY_OF_MONTH) + var monthS = cal.get(Calendar.MONTH)+1 + var yearS = cal.get(Calendar.YEAR) + + var IDFraseS = -2 /* Error if not changes */ + + lateinit var mRewardedVideoAd: RewardedVideoAd + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main); + + P = Prefs(applicationContext) + db = SQLiteHelper(applicationContext) + + + /* Ads */ + // Initialize the Mobile Ads SDK. + MobileAds.initialize(this, this.getString(R.string.admob_id)) + + mRewardedVideoAd = MobileAds.getRewardedVideoAdInstance(this) + mRewardedVideoAd.setRewardedVideoAdListener(this); + + preloadAd() + + /* Check if first time or not, for welcoming user */ + if (P.getFirstTime() == true){ + alert("Bienvenido a Frases de anime! Un lugar donde puedes descubrir las frases de tus animes favoritos, todos los días!\n" + + "Selecciona de la siguiente lista los que más te gusten para empezar."){ + positiveButton("Empezemos") {startActivity()} + }.show() + + }else { + /* Check if no anime is selected */ + if (db.getCountAnimeSel() == 0) { + alert("No tienes ningún anime seleccionado, ¿Quieres seleccionarlos ahora?", "No hay animes seleccionados") { + positiveButton("Si") { startActivity() } + negativeButton("No") { } + }.show() + } + } + + + txt_sentence.setMovementMethod(ScrollingMovementMethod()) //Make sentence text scrollable + + /* Button listeners */ + btn_fav.setOnClickListener() { + db.setFav(IDFraseS) + if (db.getFav(IDFraseS) == true) { + btn_fav.setBackgroundResource(R.drawable.star_on); + }else{ + btn_fav.setBackgroundResource(R.drawable.star_off); + } + } + + btn_share.setOnClickListener() { + share("${txt_sentence.text} (${txt_anime.text}) \n ${this.getString(R.string.sharefrom)}") + } + + btn_change.setOnClickListener() { + if (mRewardedVideoAd.isLoaded() && IDFraseS > 0) { + mRewardedVideoAd.show(); + }else if (IDFraseS <= 0) { + alert("Aquí no puedes ver una frase aún, no sirve de nada pulsar el botón") { + positiveButton("Ok") { } + }.show() + }else{ + alert("Aún no hay un anuncio disponible, espere") { + positiveButton("Ok") { } + }.show() + preloadAd() + } + } + + + btn_prev.setOnClickListener() { + //toast("Anterior") + if (dayS == 1){ + if (monthS == 1){ + monthS=12 + yearS -= 1 + }else{ + monthS -=1 + } + if (monthS in arrayOf(1, 3, 5,7,8,10,12)){ + dayS = 31 + }else if (monthS == 2){ + dayS = 28 + }else{ + dayS = 30 + } + }else{ + dayS -=1 + } + IDFraseS = db.getIDFrase(yearS,monthS, dayS) + changeData() + } + + btn_next.setOnClickListener() { + //toast("Siguiente") + if ((monthS in arrayOf(1, 3, 5,7,8,10,12) && dayS == 31) || (monthS == 2 && dayS == 28)){ + monthS += 1 + dayS = 1 + }else if (dayS == 30 && monthS in arrayOf(2,4,6,9,11) ){ + monthS += 1 + dayS = 1 + }else{ + dayS +=1 + } + + IDFraseS = db.getIDFrase(yearS,monthS, dayS) + changeData() + } + } + + /** At onResume, called after onCreate and in resume, check day and put sentence */ + override fun onResume() { + super.onResume() + cal = Calendar.getInstance() //Because it can be changed the day + IDFraseS = db.getIDFrase(yearS,monthS, dayS) + changeData() + } + + /** "Close" app on back button */ + override fun onBackPressed() { + moveTaskToBack(true) + } + + /** It returns the month name */ + fun getMonth(month: Int): String { + val str = DateFormatSymbols().getMonths()[month - 1] + return str.capitalize() + } + + /** This function replaces texts of main activity to sentence using IDFraseS var + */ + fun changeData(){ + txt_sentence.setText(db.getSentenceFromIDF(IDFraseS)) + txt_anime.setText(db.getAnimeFromIDF(IDFraseS)) + //txt_date.setText("Frase de hoy") + txt_date.setText("Frase del $dayS de ${getMonth(monthS)}") + /* Set fav icon */ + if (db.getFav(IDFraseS) == true) { + btn_fav.setBackgroundResource(R.drawable.star_on); + }else{ + btn_fav.setBackgroundResource(R.drawable.star_off); + } + } + + /** This changes sentence of day you are seeing */ + override fun onRewarded(reward: RewardItem) { + alert { + customView { + linearLayout { + imageView { + imageResource = R.drawable.gato + }.lparams(width = matchParent) { + margin = dip(4) + } + } + } + positiveButton("Cambiar la frase") { + /* First remove sentence, then load a new one, and change data */ + db.delSentence(yearS,monthS, dayS) + IDFraseS = db.getIDFrase(yearS,monthS, dayS) + changeData() + } + }.show() + } + + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.main, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean{ + when (item.getItemId()) { + R.id.favs -> { + startActivity() + return true + } + R.id.anime -> { + startActivity() + return true + } + R.id.options -> { + startActivity() + return true + } + R.id.about -> { + startActivity() + return true + } + R.id.exit -> { + moveTaskToBack(true) /* Returns to "desktop" */ + return super.onOptionsItemSelected(item) + } + else -> return super.onOptionsItemSelected(item) + } + } + + + fun preloadAd() { + mRewardedVideoAd.loadAd(this.getString(R.string.admob_videoid), AdRequest.Builder().build()); + } + +} diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/OptsActivity.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/OptsActivity.kt new file mode 100644 index 0000000..531c99e --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/OptsActivity.kt @@ -0,0 +1,99 @@ +package com.jkanetwork.st.frasesdeanime + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.content.Context +import android.widget.ArrayAdapter +import org.jetbrains.anko.* +import kotlinx.android.synthetic.main.activity_opts.* +import android.content.Intent +import java.util.* +import android.content.BroadcastReceiver + +/** + * Created by kprkpr on 30/08/17. + */ +class OptsActivity : AppCompatActivity(){ + + lateinit var P: Prefs + + val cont = this + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_opts) + + P = Prefs(applicationContext) + + val hours = ArrayList() + val minutes = ArrayList() + + for (i in 0..23){ + hours.add(i) + } + for (i in 0..59){ + minutes.add(i) + } + + var hoursAdapter = ArrayAdapter(this,android.R.layout.simple_list_item_1,hours) + var minutesAdapter = ArrayAdapter(this,android.R.layout.simple_list_item_1,minutes) + + hoursAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + minutesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + + spinnerHour.adapter = hoursAdapter + spinnerMinute.adapter = minutesAdapter + + spinnerHour.setSelection(P.getHour()) + spinnerMinute.setSelection(P.getMinute()) + + chk_alarm.setChecked(P.getNotifOn()) + chk_persistent.setChecked(P.getPersNotifOn()) + + setEnabledParts() + + /* Button listeners */ + chk_alarm.setOnClickListener() { + setEnabledParts() + } + chk_persistent.setOnClickListener() { + setEnabledParts() + } + + btn_SaveNotif.setOnClickListener() { + + P.setHour(spinnerHour.getSelectedItem().toString().toInt()) + P.setMinute(spinnerMinute.getSelectedItem().toString().toInt()) + + P.setNotifOn(chk_alarm.isChecked()) + P.setPersNotifOn(chk_persistent.isChecked()) + + val nm = applicationContext.notificationManager + nm.cancel(1); //Canel notif before service going on, for persistent notification problems + putAlarm(this) + if (chk_persistent.isChecked() && chk_alarm.isChecked()) { + this.startService(Intent(this, AlarmNotif::class.java)) + } + + } + } + + + fun setEnabledParts() { + if (chk_alarm.isChecked()) { + chk_persistent.setEnabled(true) + /* This other checks is chk_persistent checked dependent */ + if (chk_persistent.isChecked()) { + spinnerHour.setEnabled(false) + spinnerMinute.setEnabled(false) + } else { + spinnerHour.setEnabled(true) + spinnerMinute.setEnabled(true) + } + } else { + chk_persistent.setEnabled(false) + spinnerHour.setEnabled(false) + spinnerMinute.setEnabled(false) + } + } +} diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/PagerActivity.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/PagerActivity.kt new file mode 100644 index 0000000..d9dd7cc --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/PagerActivity.kt @@ -0,0 +1,44 @@ +/** DISABLED +package com.jkanetwork.st.frasesdeanime + +import android.content.Context +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.View +import org.jetbrains.anko.* +import android.widget.Toast +import android.support.v4.content.ContextCompat.startActivity +import android.content.Intent +import android.widget.AdapterView + +import kotlinx.android.synthetic.main.activity_pager.* + +/** + * Created by kprkpr on 25/07/17. + * Help: https://www.youtube.com/watch?v=x-M1L6tcsLo + */ + +class PagerActivity : AppCompatActivity(){ + + val act = this + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_pager) + + img.setImageResource(R.drawable.drawable); + + /* Button listeners */ + btn_next.setOnClickListener() { + startActivity() + } + + } + + /** On back go to main */ + override fun onBackPressed() { + startActivity() + } +} + + + */ diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/Prefs.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/Prefs.kt new file mode 100644 index 0000000..4660bf2 --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/Prefs.kt @@ -0,0 +1,54 @@ +package com.jkanetwork.st.frasesdeanime + +import android.content.Context +import android.content.SharedPreferences + +/** + * Created by kprkpr on 18/07/17. + * Help of: http://blog.teamtreehouse.com/making-sharedpreferences-easy-with-kotlin + */ +class Prefs (context: Context){ + var prefs: SharedPreferences = context.getSharedPreferences("com.jkanetwork.st.com.jkanetwork.st.frasesdeanime.PREFS", 0); + + fun getHour(): Int { + return prefs.getInt("HOUR", 8) + } + + fun setHour(x: Int) { + prefs.edit().putInt("HOUR", x).commit() + } + + fun getMinute(): Int { + return prefs.getInt("MINUTE", 0) + } + + fun setMinute(x: Int) { + prefs.edit().putInt("MINUTE", x).commit() + } + + fun getNotifOn(): Boolean { + return prefs.getBoolean("NOTIFON", true) + } + + fun setNotifOn(b: Boolean) { + prefs.edit().putBoolean("NOTIFON", b).commit() + } + + fun getPersNotifOn(): Boolean { + return prefs.getBoolean("PERSNOTIFON", false) + } + + fun setPersNotifOn(b: Boolean) { + prefs.edit().putBoolean("PERSNOTIFON", b).commit() + } + + /** Esto devuelve true solo la primera vez */ + fun getFirstTime(): Boolean { + if (prefs.getBoolean("FIRSTTIME",true) == true){ + prefs.edit().putBoolean("FIRSTTIME", false).commit() + return true; + }else{ + return false; + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/SQLiteHelper.kt b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/SQLiteHelper.kt new file mode 100644 index 0000000..7298a54 --- /dev/null +++ b/app/src/main/kotlin/com/jkanetwork/st/frasesdeanime/SQLiteHelper.kt @@ -0,0 +1,269 @@ +package com.jkanetwork.st.frasesdeanime + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import org.jetbrains.anko.db.* +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.util.Calendar +import android.util.Log + +/** + * Created by kprkpr on 18/07/17. + */ + +/** ctx, DB.NAME, null, DB.Version */ +class SQLiteHelper(ctx: Context) : ManagedSQLiteOpenHelper(ctx, "otaku.sqlite", null, 2) { + val context = ctx + val db = this.getWritableDatabase() + + // Hacer que los números al pasar a String tengan X digitos + fun Int.format(digits: Int) = java.lang.String.format("%0${digits}d", this) + companion object { + private var instance: SQLiteHelper? = null + + @Synchronized + fun getInstance(ctx: Context): SQLiteHelper { + if (instance == null) { + instance = SQLiteHelper(ctx.applicationContext) + } + return instance!! + } + } + + override fun onCreate(db: SQLiteDatabase) { + // Here you create tables + db.execSQL("CREATE TABLE FRASES (" + + "_id INTEGER PRIMARY KEY," + + "Frase TEXT NOT NULL UNIQUE," + + "IDAnime INTEGER)") + db.createTable("ANIMES", true, + "_id" to INTEGER + PRIMARY_KEY, + "Anime" to TEXT + UNIQUE) + db.execSQL("CREATE TABLE FRASESVI (" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + + "TDay TEXT NOT NULL UNIQUE," + + "IDFrase INTEGER)") + db.createTable("FRASESFAV", true, + "IDFrase" to INTEGER + UNIQUE) + db.createTable("ANIMESON", true, + "IDAnime" to INTEGER + UNIQUE) + db.execSQL(readSqldata("sqlfrases")) + db.execSQL(readSqldata("sqlanimes")) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + // Here you can upgrade tables, as usual + db.execSQL("DELETE FROM FRASES") + db.execSQL("DELETE FROM ANIMES") + db.execSQL(readSqldata("sqlfrases")) + db.execSQL(readSqldata("sqlanimes")) + } + + + fun readSqldata(str: String): String { + //getting the file + val inputStream: InputStream + if (str == "sqlfrases") { + inputStream = context.getResources().openRawResource(R.raw.sqlfrases) + } else { + inputStream = context.getResources().openRawResource(R.raw.sqlanimes) + } + val byteArrayOutputStream = ByteArrayOutputStream() + try { + var i = inputStream.read() + while (i != -1) { + byteArrayOutputStream.write(i) + i = inputStream.read() + } + inputStream.close() + + } catch (e: IOException) { + e.printStackTrace() + } + return byteArrayOutputStream.toString() + } + + /** Returns a boolean saying if a sentence is faved or not */ + fun getFav (IDFrase: Int): Boolean { + val query = db.rawQuery("SELECT IDFrase FROM FRASESFAV WHERE IDFrase = '$IDFrase'", null) + val res = query.getCount() + query.close() + if (res > 0){ + return true + }else{ + return false + } + } + + /** Changes the state of fav of a sentence + * @param IDFrase: Sentence + */ + fun setFav (IDFrase: Int) { + if (getFav(IDFrase) == false){ + db.insert("FRASESFAV","IDFrase" to IDFrase) + }else{ + db.execSQL("DELETE FROM FRASESFAV WHERE IDFrase='$IDFrase'") + } + } + + fun getSentenceFromIDF (IDFrase: Int): String { + if (IDFrase == 0){ return "No hay más frases disponibles..."} + if (IDFrase == -1){ return "No puedes ver aún esta frase"} + if (IDFrase == -2){ return "Error inesperado"} + val c = db.rawQuery("SELECT Frase FROM FRASES WHERE _id = '$IDFrase'", null) + c.moveToFirst() + val ret = c.getString(0) + c.close() + return ret + } + + fun getAnimeFromIDF (IDFrase: Int): String { + if (IDFrase <= 0){ return "..."} + val c = db.rawQuery("SELECT Anime FROM ANIMES WHERE _id = (SELECT IDAnime FROM FRASES WHERE _id = '$IDFrase')", null) + c.moveToFirst() + val ret = c.getString(0) + c.close() + return ret + } + + fun getIDAnimeFromName (Name: String): Int { + var query = db.select("ANIMES", "_id").whereArgs("Anime = '$Name'") + return query.parseSingle(IntParser) + } + + + + fun getIDFrase (year: Int,month: Int, day: Int): Int { + val cal = Calendar.getInstance() + /* First Integer to Strings of format YYYYMMDD for store and compare */ + val diaArg = "${year}${month.format(2)}${day.format(2)}" + val diaAct = "${cal.get(Calendar.YEAR)}${(cal.get(Calendar.MONTH)+1).format(2)}${cal.get(Calendar.DAY_OF_MONTH).format(2)}" + + /* Query db looking for the sentence. If there are not loaded yet, see (else if) if they can show a new one, if criteria is true. If not, not show sentence */ + var query = db.rawQuery("SELECT IDFrase FROM FRASESVI WHERE TDay = '$diaArg'", null) + if (query.getCount() > 0){ + query.moveToFirst() + val ret = query.getInt(0) + query.close() + return ret + }else if (diaArg == diaAct || diaArg.toInt() < diaAct.toInt() && db.rawQuery("SELECT IDFrase FROM FRASESVI WHERE Tday < '$diaArg'", null).count > 0 ) { + try { + query = db.rawQuery("SELECT _id FROM FRASES WHERE IDAnime IN (SELECT IDAnime FROM ANIMESON) AND _id NOT IN (SELECT IDFrase FROM FRASESVI) ORDER BY RANDOM() LIMIT 1", null) + query.moveToFirst() + db.insert("FRASESVI", + "TDay" to diaArg, + "IDFrase" to query.getString(0)) + val ret = query.getInt(0) + query.close() + return ret + } + catch (e: Exception) { + return 0 //No more sentences + } + }else{ + return -1 //Day not yet + } + } + + fun getIDFraseHoy(): Int { + val cal = Calendar.getInstance() + //Month + 1 because Android starts with month=0 + return getIDFrase(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH)) + } + + fun getAllIDFavs(): Array{ + var query = db.select("FRASESFAV", "IDFrase") + var list = query.parseList(IntParser) + return list.toTypedArray() + } + + fun getAllAnimeIDs(): Array{ + var query = db.select("ANIMES", "_id").orderBy("Anime", SqlOrderDirection.ASC) + var list = query.parseList(IntParser) + return list.toTypedArray() + } + + fun getAllAnimeNames(): Array{ + var query = db.select("ANIMES", "Anime").orderBy("Anime", SqlOrderDirection.ASC) + var list = query.parseList(StringParser) + return list.toTypedArray() + } + + /** Return if the anime asked by [IDA] is selected */ + fun getAnimeSel(IDA: Int): Boolean{ + val query = db.rawQuery("SELECT IDAnime FROM ANIMESON WHERE IDAnime = '$IDA'", null) + val res = query.getCount() + query.close() + if (res > 0){ + return true + }else{ + return false + } + } + + /** Changes the state of a selected or not anime + * @param IDAnime: Anime + */ + fun setAnimeSel (IDAnime: Int) { + if (getAnimeSel(IDAnime) == false){ + db.insert("ANIMESON","IDAnime" to IDAnime) + }else{ + db.execSQL("DELETE FROM ANIMESON WHERE IDAnime='$IDAnime'") + } + } + + + /** Return how much animes are selected (For knowing if 0) */ + fun getCountAnimeSel(): Int{ + val query = db.rawQuery("SELECT COUNT(*) FROM ANIMESON", null) + query.moveToFirst() + val ret = query.getInt(0) + query.close(); + return ret; + } + + /** Deletes the sentence of the day passed as argument yearS,monthS, dayS + * @param year: Year selected + * @param month: Month selected + * @param day: Day selected + */ + fun delSentence (year: Int, month: Int, day: Int) { + val diaArg = "${year}${month.format(2)}${day.format(2)}" + db.execSQL("DELETE FROM FRASESVI WHERE TDay='$diaArg'") + } + + + /** This funcion selects or desselects all animes from selected to show */ + fun setAllAnimeSel(){ + var selectAll = getAllAnimeSel(); //Know if anyone is select (true/false) + //First, unset all, and after that, select all if they were not before. (Why? Because if someone are select, inserting again will crash) + db.execSQL("DELETE FROM ANIMESON") + if (selectAll == false){ //Set all + var animeIDs = getAllAnimeIDs() + + for (pos in animeIDs){ + db.insert("ANIMESON", + "IDAnime" to animeIDs[pos-1]) //Pos is a count, arrays start at 0 + } + } + } + + /** It returns if all animes are selected or not */ + fun getAllAnimeSel(): Boolean{ + var set = getCountAnimeSel(); + var countanime = getAllAnimeIDs().size; + if (set == countanime){ + return true; + }else{ + return false; + } + + } + +} + +// Access property for Context +val Context.database: SQLiteHelper + get() = SQLiteHelper.getInstance(applicationContext) \ No newline at end of file diff --git a/app/src/main/res/drawable/cerebro.png b/app/src/main/res/drawable/cerebro.png new file mode 100644 index 0000000..51b2cbd Binary files /dev/null and b/app/src/main/res/drawable/cerebro.png differ diff --git a/app/src/main/res/drawable/gato.png b/app/src/main/res/drawable/gato.png new file mode 100644 index 0000000..c9b23dd Binary files /dev/null and b/app/src/main/res/drawable/gato.png differ diff --git a/app/src/main/res/drawable/opts.png b/app/src/main/res/drawable/opts.png new file mode 100644 index 0000000..43fddc0 Binary files /dev/null and b/app/src/main/res/drawable/opts.png differ diff --git a/app/src/main/res/drawable/star_off.png b/app/src/main/res/drawable/star_off.png new file mode 100644 index 0000000..75348ca Binary files /dev/null and b/app/src/main/res/drawable/star_off.png differ diff --git a/app/src/main/res/drawable/star_on.png b/app/src/main/res/drawable/star_on.png new file mode 100644 index 0000000..fa6afac Binary files /dev/null and b/app/src/main/res/drawable/star_on.png differ diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..dbe8a46 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1d7b4a2 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +