commit 06ea1c88e4786a3aea75f05aecbf842cffee21bb Author: denizk0461 Date: Mon May 8 11:27:43 2023 +0200 Initial push; upped AGP to 8.0.1, Kotlin plugin to 1.8.20, as well as updated all dependencies. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..7b84a46 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Text Basic \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..a0de2a1 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..69e8615 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..833f612 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f2e5f94 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,53 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 33 + + defaultConfig { + applicationId "com.denizk0461.textbasic" + minSdk 23 + targetSdk 33 + versionCode 6 + versionName "1.3.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + ndkVersion "21.3.6528147" + + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + + ndk { + debugSymbolLevel 'SYMBOL_TABLE' + } + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + viewBinding.enabled = true + namespace 'com.denizd.textbasic' +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.preference:preference:1.2.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# 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 \ No newline at end of file diff --git a/app/release/2 (1.0.0).aab b/app/release/2 (1.0.0).aab new file mode 100644 index 0000000..4ed0992 Binary files /dev/null and b/app/release/2 (1.0.0).aab differ diff --git a/app/release/3 (1.1.0).aab b/app/release/3 (1.1.0).aab new file mode 100644 index 0000000..c2b191e Binary files /dev/null and b/app/release/3 (1.1.0).aab differ diff --git a/app/release/4 (1.2.0).aab b/app/release/4 (1.2.0).aab new file mode 100644 index 0000000..4727a01 Binary files /dev/null and b/app/release/4 (1.2.0).aab differ diff --git a/app/release/5 (1.2.1).aab b/app/release/5 (1.2.1).aab new file mode 100644 index 0000000..1285f7f Binary files /dev/null and b/app/release/5 (1.2.1).aab differ diff --git a/app/src/androidTest/java/com/denizd/textbasic/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/denizd/textbasic/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..cd50a7c --- /dev/null +++ b/app/src/androidTest/java/com/denizd/textbasic/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.denizd.textbasic + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.denizd.textbasic", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cb56bde --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..48ced8c Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/denizd/textbasic/ColorTransparentUtils.kt b/app/src/main/java/com/denizd/textbasic/ColorTransparentUtils.kt new file mode 100644 index 0000000..779eb0a --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/ColorTransparentUtils.kt @@ -0,0 +1,55 @@ +package com.denizd.textbasic + +import android.R +import java.util.* +import kotlin.math.roundToInt + +/** + * Created by Hemant chand on 05/07/17. + */ +object ColorTransparentUtils { + // This default color int + const val defaultColorID = R.color.black + const val defaultColor = "000000" + const val TAG = "ColorTransparentUtils" + + /** + * This method convert numver into hexa number or we can say transparent code + * + * @param trans number of transparency you want + * @return it return hex decimal number or transparency code + */ + fun convert(trans: Int): String { + val hexString = Integer.toHexString((255 * trans / 100.0).roundToInt()) + return (if (hexString.length < 2) "0" else "") + hexString + } + + fun transparentColor(colorCode: Int, trans: Int): String { + return convertIntoColor(colorCode, trans) + } + + /** + * Convert color code into transparent color code + * + * @param colorCode color code + * @param transCode transparent number + * @return transparent color code + */ + fun convertIntoColor(colorCode: Int, transCode: Int): String { + // convert color code into hexa string and remove starting 2 digit + var color = defaultColor + try { + color = Integer.toHexString(colorCode).uppercase(Locale.getDefault()).substring(2) + } catch (ignored: Exception) { + } + return if (color.isNotEmpty() && transCode < 101) { + if (color.trim { it <= ' ' }.length == 6) { + "#" + convert(transCode) + color + } else { +// Log.d(TAG, "Color is already with transparency") + convert(transCode) + color + } + } else "#" + Integer.toHexString(defaultColorID).uppercase(Locale.getDefault()).substring(2) + // if color is empty or any other problem occur then we return deafult color; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/DriveAgent.kt b/app/src/main/java/com/denizd/textbasic/DriveAgent.kt new file mode 100644 index 0000000..f5ffdeb --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/DriveAgent.kt @@ -0,0 +1,14 @@ +package com.denizd.textbasic + +import android.app.backup.* + +class DriveAgent : BackupAgentHelper() { + + override fun onCreate() { + super.onCreate() + + SharedPreferencesBackupHelper(this, QuoteStorage.PREF_NAME).also { + addHelper(QuoteStorage.PREF_BACKUP_KEY, it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/MainActivity.kt b/app/src/main/java/com/denizd/textbasic/MainActivity.kt new file mode 100644 index 0000000..323f3d5 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/MainActivity.kt @@ -0,0 +1,97 @@ +package com.denizd.textbasic + +import android.app.backup.BackupManager +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentTransaction +import androidx.lifecycle.Lifecycle +import com.denizd.textbasic.databinding.ActivityMainBinding +import com.denizd.textbasic.fragment.GuideFragment +import com.denizd.textbasic.fragment.QuoteFragment +import com.denizd.textbasic.fragment.SettingsFragment +import com.denizd.textbasic.widget.TextCanvasWidget +import com.google.android.material.snackbar.Snackbar + +class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(this.layoutInflater) + setContentView(binding.root) + +// binding.buttonBackupDownload.setOnClickListener { +// +// Snackbar.make(binding.rootLayout, R.string.snack_backup_download, Snackbar.LENGTH_LONG).show() +// } + + binding.buttonBackupUpload.setOnClickListener { + BackupManager(this).dataChanged() + Snackbar.make(binding.rootLayout, R.string.snack_backup_upload, Snackbar.LENGTH_LONG).show() + } + + binding.bottomNav.setOnItemSelectedListener { item -> + loadFragment(item.itemId) + } + } + + override fun onResume() { + super.onResume() + + if (supportFragmentManager.fragments.size == 0) { + binding.bottomNav.selectedItemId = R.id.quotes + loadFragment(R.id.quotes) + } + } + + private fun loadFragment(type: Int): Boolean { + val fragment = when (type) { + R.id.quotes -> QuoteFragment() + R.id.guide -> GuideFragment() + R.id.settings -> SettingsFragment() + else -> QuoteFragment() + }//supportFragmentManager.findFragmentByTag(type) ?: viewModel.getFragment(type) + + if (supportFragmentManager.fragments.isNotEmpty() && + supportFragmentManager.fragments[supportFragmentManager.fragments.size-1] + .tag?.equals(type.toString()) == true + ) { + return false + } + + if (fragment.lifecycle.currentState != Lifecycle.State.INITIALIZED) { + return false + } + + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, fragment, type.toString()) +// .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .commit() + +// with (supportFragmentManager) { +// beginTransaction() +// .hide(fragments[fragments.size - 1]) // should this be 0? +// .show(findFragmentByTag(type) ?: TODO()) +// .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) +// .commit() +// } + return true + } + + override fun onPause() { + + arrayOf(TextCanvasWidget::class.java).forEach { widget -> + sendBroadcast(Intent(this, widget).also { intent -> + intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, AppWidgetManager.getInstance(this) + .getAppWidgetIds(ComponentName(application, widget))) + }) + } + + super.onPause() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/Quote.kt b/app/src/main/java/com/denizd/textbasic/Quote.kt new file mode 100644 index 0000000..0008b09 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/Quote.kt @@ -0,0 +1,3 @@ +package com.denizd.textbasic + +data class Quote(val id: Int, var text: String) \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/QuoteAdapter.kt b/app/src/main/java/com/denizd/textbasic/QuoteAdapter.kt new file mode 100644 index 0000000..9d693f1 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/QuoteAdapter.kt @@ -0,0 +1,71 @@ +package com.denizd.textbasic + +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.button.MaterialButton + +class QuoteAdapter(quotes: List) : RecyclerView.Adapter() { + + private var mutableQuotes: MutableList = quotes.toMutableList() + + class QuoteViewHolder(view: View) : RecyclerView.ViewHolder(view)/*, View.OnClickListener, View.OnLongClickListener*/ { + + val quote: TextView = view.findViewById(R.id.quote) + val delete: ImageButton = view.findViewById(R.id.delete_button) + } + + override fun getItemViewType(position: Int): Int { + return /*if (position == itemCount - 1) 1 else*/ 0 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuoteViewHolder { + val v = LayoutInflater.from(parent.context).inflate(R.layout.quote_entry, parent, false) + return QuoteViewHolder(v) + } + + override fun onBindViewHolder(holder: QuoteViewHolder, position: Int) { + val currentItem = mutableQuotes[position] + + holder.quote.text = currentItem + + holder.quote.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} + override fun afterTextChanged(p0: Editable?) {} + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + mutableQuotes[holder.layoutPosition] = p0.toString() + } + }) + + holder.delete.setOnClickListener { + mutableQuotes.removeAt(holder.layoutPosition) + notifyItemRemoved(holder.layoutPosition) + } + } + + override fun getItemCount(): Int = mutableQuotes.size + + fun setQuotes(quotes: List) { + this.mutableQuotes = quotes.toMutableList() + notifyDataSetChanged() + } + + fun addNewQuote() { + mutableQuotes.add("") + notifyItemInserted(mutableQuotes.size + 1) + } + + fun getAllQuotes(): Array { + return mutableQuotes.filter { s -> s.isNotBlank() }.toTypedArray() + } + + interface QuoteClickListener { + fun onQuoteClick(quotes: String) + fun onQuoteLongClick(quoteId: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/QuoteStorage.kt b/app/src/main/java/com/denizd/textbasic/QuoteStorage.kt new file mode 100644 index 0000000..7b05dbe --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/QuoteStorage.kt @@ -0,0 +1,134 @@ +package com.denizd.textbasic + +import android.content.Context +import android.content.SharedPreferences +import android.graphics.Color +import android.graphics.Typeface +import android.util.Log +import androidx.preference.PreferenceManager + +class QuoteStorage(context: Context) { + + companion object { + + const val PREF_NAME = "textbasicprefs" + const val PREF_BACKUP_KEY = "textbasicprefskey" + const val SEPARATOR = '\uFFFF' + + private const val KEY_QUOTES = "quotes" + private const val KEY_QUOTE_COUNTER = "quotecounter" + private const val KEY_TEXT_SIZE = "textsize" + private const val KEY_INVERTED = "inverted" + private const val KEY_HIGH_CONTRAST = "hicontrast" + private const val KEY_TRANSPARENCY = "transparency" + private const val KEY_RANDOM = "random" + private const val KEY_WIDGET_POSITION = "widgetposition" + private const val KEY_BACKGROUND_TYPE = "backgroundtype" + private const val KEY_TYPEFACE = "typeface" + private const val KEY_TYPEFACE_STYLE = "typefacestyle" + private const val KEY_AVG_WIDTH = "avgwidth" + private const val KEY_AVG_HEIGHT = "avgheight" + private const val KEY_OUTLINE_SIZE = "outlinesize" + + val prefGroups = arrayOf( + KEY_QUOTES, KEY_QUOTE_COUNTER, KEY_TEXT_SIZE, KEY_INVERTED, KEY_HIGH_CONTRAST, + KEY_TRANSPARENCY, KEY_RANDOM, KEY_WIDGET_POSITION + ) + + +// val transparencyValues = arrayOf( +// "0", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", +// "55", "60", "65", "70", "75", "80", "85", "90", "95", "100", +// ) + } + + private val prefs = context.getSharedPreferences(PREF_NAME, 0) + + fun getAllQuotes(): List = ((prefs.getString(KEY_QUOTES, "")) ?: "").split(SEPARATOR).toList() + + fun getRandomQuote(): String { + val quotes = getAllQuotes() + return quotes.random() + } + + fun getNextQuote(): String { + val currentQuoteCounter: Int = prefs.getInt(KEY_QUOTE_COUNTER, 0) + val quotes = getAllQuotes() + val nextQuoteCounter: Int = if (currentQuoteCounter >= quotes.size - 1) 0 else currentQuoteCounter + 1 + prefs.edit().putInt( + KEY_QUOTE_COUNTER, + nextQuoteCounter + ).apply() + return quotes[nextQuoteCounter] + } + + fun getTextSize() = prefs.getInt(KEY_TEXT_SIZE, 18) + + fun isInvertedEnabled(): Boolean = prefs.getBoolean(KEY_INVERTED, false) + fun isHighContrastEnabled(): Boolean = prefs.getBoolean(KEY_HIGH_CONTRAST, false) + fun getTransparency(): Int = prefs.getInt(KEY_TRANSPARENCY, 100) + fun isOrderRandom(): Boolean = prefs.getBoolean(KEY_RANDOM, false) + fun getWidgetGravity(): Int = prefs.getInt(KEY_WIDGET_POSITION, 1) + fun getBackgroundType(): Int = prefs.getInt(KEY_BACKGROUND_TYPE, 0) + fun getTypefaceIndex(): Int = prefs.getInt(KEY_TYPEFACE, 0) + fun getTypeface(): Typeface = Typeface.create( + when (getTypefaceIndex()) { + 1 -> Typeface.SERIF + 2 -> Typeface.MONOSPACE + else -> Typeface.SANS_SERIF + }, + getTypefaceStyle() + ) + fun getTypefaceStyle(): Int = prefs.getInt(KEY_TYPEFACE_STYLE, 0) + fun getOutlineSize(): Float = prefs.getFloat(KEY_OUTLINE_SIZE, 8f) + + // Pair + fun getColours( + isInverted: Boolean = isInvertedEnabled(), + transparency: Int = getTransparency(), + context: Context + ): Pair { + val colours = when (isInverted) { + true -> Pair(R.color.widget_dark, R.color.widget_light) + false -> Pair(R.color.widget_light, R.color.widget_dark) + } + + return Pair( + context.getColor(colours.first),//Color.parseColor(ColorTransparentUtils.transparentColor(colours.first, 10)), + Color.parseColor(ColorTransparentUtils.transparentColor(context.getColor(colours.second), transparency * 5)) + ) + } + + fun setSize(size: Pair) { + prefs.edit().apply { + putInt(KEY_AVG_WIDTH, size.first) + putInt(KEY_AVG_HEIGHT, size.second) + }.apply() + } + + fun getSize(): Pair = Pair(prefs.getInt(KEY_AVG_WIDTH, 1), prefs.getInt(KEY_AVG_HEIGHT, 1)) + + fun saveSettings( + textSize: Int, inverted: Boolean, transparency: Int, random: Boolean, + widgetPosition: Int, backgroundType: Int, typefaceIndex: Int, typefaceStyleIndex: Int, + outlineSize: Float + ) { + prefs.edit().apply { + putInt(KEY_TEXT_SIZE, textSize) + putBoolean(KEY_INVERTED, inverted) + putInt(KEY_TRANSPARENCY, transparency) + putBoolean(KEY_RANDOM, random) + putInt(KEY_WIDGET_POSITION, widgetPosition) + putInt(KEY_BACKGROUND_TYPE, backgroundType) + putInt(KEY_TYPEFACE, typefaceIndex) + putInt(KEY_TYPEFACE_STYLE, typefaceStyleIndex) + putFloat(KEY_OUTLINE_SIZE, outlineSize) + }.apply() + } + + fun saveQuotes(quotes: Array) { + prefs.edit() + .putString(KEY_QUOTES, quotes.joinToString(SEPARATOR.toString())) + .apply() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/TextWidget.kt b/app/src/main/java/com/denizd/textbasic/TextWidget.kt new file mode 100644 index 0000000..2d99001 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/TextWidget.kt @@ -0,0 +1,71 @@ +package com.denizd.textbasic + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.graphics.Typeface +import android.util.TypedValue +import android.view.Gravity +import android.widget.RemoteViews +import android.widget.TextView + +class TextWidget : AppWidgetProvider() { + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + + val remoteViews = configureViews(context, appWidgetIds) + // Perform this loop procedure for each App Widget that belongs to this provider + appWidgetIds.forEach { appWidgetId -> + // Tell the AppWidgetManager to perform an update on the current app widget + appWidgetManager.updateAppWidget(appWidgetId, remoteViews) + } + } + + private fun configureViews(context: Context, appWidgetIds: IntArray): RemoteViews = + RemoteViews(context.packageName, R.layout.widget_text).apply { + + val storage = QuoteStorage(context) + + val quote = if (storage.isOrderRandom()) { + storage.getRandomQuote() + } else { + storage.getNextQuote() + } + + setTextViewText(R.id.widget_text, quote.ifBlank { context.getString(R.string.info_add_text) }) + setTextViewTextSize(R.id.widget_text, TypedValue.COMPLEX_UNIT_SP, storage.getTextSize().toFloat()) + + val colours = storage.getColours(context = context) + + setTextColor(R.id.widget_text, colours.first) + setInt(R.id.background, "setBackgroundColor", colours.second) + + val gravity = when (storage.getWidgetGravity()) { + 0 -> Gravity.START or Gravity.TOP + 1 -> Gravity.CENTER_HORIZONTAL or Gravity.TOP + 2 -> Gravity.END or Gravity.TOP + 3 -> Gravity.START or Gravity.CENTER_VERTICAL + 4 -> Gravity.CENTER + 5 -> Gravity.END or Gravity.CENTER_VERTICAL + 6 -> Gravity.START or Gravity.BOTTOM + 7 -> Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM + 8 -> Gravity.END or Gravity.BOTTOM + else -> Gravity.CENTER + } + + setInt(R.id.root_layout, "setGravity", gravity) + + setOnClickPendingIntent( + R.id.root_layout, + Intent(context, TextWidget::class.java).let { intent -> + intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) + PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_IMMUTABLE + ) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/VerticalTextView.kt b/app/src/main/java/com/denizd/textbasic/VerticalTextView.kt new file mode 100644 index 0000000..79d9add --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/VerticalTextView.kt @@ -0,0 +1,41 @@ +package com.denizd.textbasic + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.util.AttributeSet + +class VerticalTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0 +) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyle) { + + private var _width = 0 + private var _height = 0 + private val _bounds: Rect = Rect() + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + // vise versa + _height = measuredWidth + _width = measuredHeight + setMeasuredDimension(_width, _height) + } + + override fun onDraw(canvas: Canvas) { + canvas.save() + canvas.translate(_width.toFloat(), _height.toFloat()) + canvas.rotate(-90f) + val paint = paint + paint.color = textColors.defaultColor + val text = text() + paint.getTextBounds(text, 0, text.length, _bounds) + canvas.drawText(text, compoundPaddingLeft.toFloat(), (_bounds.height() - _width) / 2f, paint) + canvas.restore() + } + + private fun text(): String { + return super.getText().toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/fragment/BaseFragment.kt b/app/src/main/java/com/denizd/textbasic/fragment/BaseFragment.kt new file mode 100644 index 0000000..b9a84c6 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/fragment/BaseFragment.kt @@ -0,0 +1,39 @@ +package com.denizd.textbasic.fragment + +import android.content.Context +import androidx.annotation.LayoutRes +import androidx.core.widget.NestedScrollView +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView + +open class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { + + private lateinit var _context: Context + + override fun onAttach(context: Context) { + super.onAttach(context) + _context = context + } + + override fun getContext(): Context = _context + +// protected fun RecyclerView.addFabScrollListener() { +// addOnScrollListener(object : RecyclerView.OnScrollListener() { +// override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { +// when { +// dy > 0 -> fab.hide() +// dy < 0 -> fab.show() +// } +// } +// }) +// } +// +// protected fun NestedScrollView.addFabScrollListener() { +// setOnScrollChangeListener { _, _, dy, _, doy -> +// when { +// dy > doy -> fab.hide() +// dy < doy -> fab.show() +// } +// } +// } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/fragment/GuideFragment.kt b/app/src/main/java/com/denizd/textbasic/fragment/GuideFragment.kt new file mode 100644 index 0000000..adb3b67 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/fragment/GuideFragment.kt @@ -0,0 +1,18 @@ +package com.denizd.textbasic.fragment + +import android.os.Bundle +import com.denizd.textbasic.R +import com.google.android.material.transition.MaterialSharedAxis + +class GuideFragment : BaseFragment(R.layout.fragment_guide) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/fragment/QuoteFragment.kt b/app/src/main/java/com/denizd/textbasic/fragment/QuoteFragment.kt new file mode 100644 index 0000000..3e5e1fb --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/fragment/QuoteFragment.kt @@ -0,0 +1,57 @@ +package com.denizd.textbasic.fragment + +import android.animation.LayoutTransition +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import com.denizd.textbasic.QuoteAdapter +import com.denizd.textbasic.QuoteStorage +import com.denizd.textbasic.util.viewBinding +import com.denizd.textbasic.R +import com.denizd.textbasic.databinding.FragmentQuoteBinding +import com.google.android.material.transition.MaterialSharedAxis + +class QuoteFragment : BaseFragment(R.layout.fragment_quote) { + + private lateinit var storage: QuoteStorage + private lateinit var quoteAdapter: QuoteAdapter + + private val binding: FragmentQuoteBinding by viewBinding(FragmentQuoteBinding::bind) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.quoteConstraintLayout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) + + storage = QuoteStorage(context) + quoteAdapter = QuoteAdapter(storage.getAllQuotes()) + + binding.quoteScroller.let { s -> + s.layoutManager = LinearLayoutManager(context) + s.adapter = quoteAdapter + } + + binding.addFab.setOnClickListener { + quoteAdapter.addNewQuote() + } + } + + private fun save() { + storage.saveQuotes(quoteAdapter.getAllQuotes()) + } + + override fun onPause() { + save() + super.onPause() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/fragment/SettingsFragment.kt b/app/src/main/java/com/denizd/textbasic/fragment/SettingsFragment.kt new file mode 100644 index 0000000..5334712 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/fragment/SettingsFragment.kt @@ -0,0 +1,319 @@ +package com.denizd.textbasic.fragment + +import android.graphics.Bitmap +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.annotation.ColorInt +import com.denizd.textbasic.ColorTransparentUtils +import com.denizd.textbasic.QuoteStorage +import com.denizd.textbasic.R +import com.denizd.textbasic.databinding.FragmentSettingsBinding +import com.denizd.textbasic.util.viewBinding +import com.denizd.textbasic.widget.CanvasText +import com.google.android.material.transition.MaterialSharedAxis +import java.lang.reflect.Field +import kotlin.math.abs + +class SettingsFragment : BaseFragment(R.layout.fragment_settings) { + + private lateinit var storage: QuoteStorage + private lateinit var gravityButtons: Array + + private var widgetGravity = 4 + private var backgroundIndex = 0 + private var typefaceIndex = 0 + private var typefaceStyleIndex = 0 + + private val binding: FragmentSettingsBinding by viewBinding(FragmentSettingsBinding::bind) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + +// binding.animationLl.animateLayoutChanges(true) + + storage = QuoteStorage(context) + + gravityButtons = arrayOf( + binding.buttonTopLeft, binding.buttonTopMiddle, binding.buttonTopRight, + binding.buttonMiddleLeft, binding.buttonMiddleMiddle, binding.buttonMiddleRight, + binding.buttonBottomLeft, binding.buttonBottomMiddle, binding.buttonBottomRight + ) + + widgetGravity = storage.getWidgetGravity() + + binding.textSizePicker.apply { + minValue = 1 + maxValue = 168 + descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS + wrapSelectorWheel = false + value = storage.getTextSize() + + setOnValueChangedListener { _, _, _ -> + updatePreview() + } + } + + binding.transparencyPicker.apply { + minValue = 0 + maxValue = 20 + val formatter = NumberPicker.Formatter { value -> + val temp = value * 5 + "" + temp + } + setFormatter(formatter) + descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS + wrapSelectorWheel = false + value = storage.getTransparency() + + // Do not touch - fix for value on spinner not showing up until touched + val f: Field = NumberPicker::class.java.getDeclaredField("mInputText") + f.isAccessible = true + val inputText: EditText = f.get(binding.transparencyPicker) as EditText + inputText.filters = arrayOfNulls(0) + + setOnValueChangedListener { _, _, _ -> + updatePreview() + } + } + + binding.outlinePicker.apply { + minValue = 0 + maxValue = 64 + descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS + wrapSelectorWheel = false + value = storage.getOutlineSize().toInt() + + setOnValueChangedListener { _, _, _ -> + updatePreview() + } + } + + binding.switchInvert.apply { + isChecked = storage.isInvertedEnabled() + + setOnCheckedChangeListener { _, _ -> + updatePreview() + } + } + + binding.switchRandom.apply { + isChecked = storage.isOrderRandom() + + setOnCheckedChangeListener { _, _ -> + updatePreview() + } + } + +// binding.switchHighContrast.apply { +// isChecked = storage.isHighContrastEnabled() +// +// setOnCheckedChangeListener { _, _ -> +// updatePreview() +// } +// } + +// binding.typefaceMenuTextview.apply { +// typefaceIndex = storage.getTypefaceIndex() +// val typefaces = context.resources.getStringArray(R.array.typefaces) +// val typefaceAdapter = ArrayAdapter( +// context, +// android.R.layout.simple_dropdown_item_1line, +// typefaces +// ) +// setAdapter(typefaceAdapter) +// setText(typefaces[typefaceIndex], false) +// +// setOnItemClickListener { adapterView, view, index, l -> +// typefaceIndex = index +// updatePreview() +// } +// } + + binding.typefaceToggleButton.apply { + typefaceIndex = storage.getTypefaceIndex() + + check(when (typefaceStyleIndex) { + 2 -> R.id.button3_monospace + 1 -> R.id.button2_serif + else -> R.id.button1_sansserif + }) + + addOnButtonCheckedListener { group, checkedId, isChecked -> + typefaceIndex = when (checkedId) { + R.id.button3_monospace -> 2 + R.id.button2_serif -> 1 + else -> 0 + } + updatePreview() + } + } + + binding.typefaceStyleToggleButton.apply { + typefaceStyleIndex = storage.getTypefaceStyle() + + check(when (typefaceStyleIndex) { + 3 -> R.id.button4_bold_italics + 2 -> R.id.button3_italics + 1 -> R.id.button2_bold + else -> R.id.button1_normal + }) + + addOnButtonCheckedListener { group, checkedId, isChecked -> + typefaceStyleIndex = when (checkedId) { + R.id.button4_bold_italics -> 3 + R.id.button3_italics -> 2 + R.id.button2_bold -> 1 + else -> 0 + } + updatePreview() + } + } + + binding.backgroundToggleButton.apply { + backgroundIndex = storage.getBackgroundType() + + check(when (backgroundIndex) { + 2 -> R.id.button3_shadow + 1 -> R.id.button2_outline + else -> R.id.button1_background + }) + + addOnButtonCheckedListener { group, checkedId, isChecked -> + backgroundIndex = when (checkedId) { + R.id.button3_shadow -> 2 + R.id.button2_outline -> 1 + else -> 0 + } + updatePreview() + } + } + + gravityButtons.forEachIndexed { index, button -> + button.setOnClickListener { + widgetGravity = index + setGravityButtons() + updatePreview() + } + } + + setGravityButtons() + updatePreview() + } + + private fun setGravityButtons() { + val transparent = context.getColor(android.R.color.transparent) + + gravityButtons.forEach { button -> + button.setBackgroundColor(transparent) + } + + val typedValue = TypedValue() + context.theme.resolveAttribute(R.attr.colorSecContainer, typedValue, true) + @ColorInt val colorTertiary = typedValue.data + + gravityButtons[widgetGravity].setBackgroundColor(colorTertiary) + } + + private fun updatePreview() { + + save() + + if (backgroundIndex == 0) { + binding.outlineTextview.visibility = View.GONE + binding.outlinePicker.visibility = View.GONE + } else { + binding.outlineTextview.visibility = View.VISIBLE + binding.outlinePicker.visibility = View.VISIBLE + if (backgroundIndex == 1) { + binding.outlineTextview.text = getString(R.string.thickness) + } else { + binding.outlineTextview.text = getString(R.string.intensity) + } + } + +// val isBackgroundDark = if (storage.isInvertedEnabled()) { // bg white +// (storage.getTransparency() * 5) <= 5 +// } else { // bg black +// (storage.getTransparency() * 5) > 5 +// } + +// Log.d("ASDF", isBackgroundDark.toString()) + +// binding.textPreviewBackground.setBackgroundColor( +// Color.parseColor(ColorTransparentUtils.transparentColor(context.getColor(if (isBackgroundDark) { +// R.color.md_theme_light_background +// } else { +// R.color.md_theme_dark_background +// }), storage.getTransparency() * 5))) + +// binding.textPreviewBackground.setBackgroundColor( +// storage.getColours( +// !storage.isInvertedEnabled(), +// abs(storage.getTransparency() - 100), +// context = context +// ).second +// ) + + binding.textPreviewBackgroundDark.setBackgroundColor( + Color.parseColor(ColorTransparentUtils.transparentColor( + context.getColor(R.color.md_theme_dark_background), + if (storage.isInvertedEnabled()) { + abs((storage.getTransparency() * 5)) + } else { + abs((storage.getTransparency() * 5) - 100) + } + )) + ) + + val bitmap = CanvasText.drawText(context, true) + + binding.textPreview.setImageBitmap(bitmap) + +// val colours = storage.getColours( +// binding.switchInvert.isChecked, +//// binding.switchHighContrast.isChecked, +// binding.transparencyPicker.value, +// context +// ) +//// binding.exampleText.textSize = binding.textSizePicker.value.toFloat() +// binding.exampleText.setTextColor(colours.first) +// binding.background.setBackgroundColor(colours.second) + +// Log.d("VALVALUE", "${(binding.transparencyPicker.value * 5).toString()} /// ${colours.second}") + } + + private fun save() { + storage.saveSettings( + binding.textSizePicker.value, + binding.switchInvert.isChecked, +// binding.switchHighContrast.isChecked, + binding.transparencyPicker.value, + binding.switchRandom.isChecked, + widgetGravity, + backgroundIndex, + typefaceIndex, + typefaceStyleIndex, + binding.outlinePicker.value.toFloat() + ) + } + + override fun onPause() { + save() + super.onPause() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/util/ActivityViewBindingDelegate.kt b/app/src/main/java/com/denizd/textbasic/util/ActivityViewBindingDelegate.kt new file mode 100644 index 0000000..cadd9a6 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/util/ActivityViewBindingDelegate.kt @@ -0,0 +1,12 @@ +package com.denizd.textbasic.util + +import android.view.LayoutInflater +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentActivity +import androidx.viewbinding.ViewBinding + +// written by Zhuinden (props to them!) +inline fun FragmentActivity.viewBinding(crossinline bindingInflater: (LayoutInflater) -> T) = + lazy(LazyThreadSafetyMode.NONE) { + bindingInflater.invoke(layoutInflater) + } \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/util/FragmentViewBindingDelegate.kt b/app/src/main/java/com/denizd/textbasic/util/FragmentViewBindingDelegate.kt new file mode 100644 index 0000000..32535df --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/util/FragmentViewBindingDelegate.kt @@ -0,0 +1,50 @@ +package com.denizd.textbasic.util + +import android.view.View +import androidx.fragment.app.Fragment +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.observe +import androidx.viewbinding.ViewBinding +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +// written by Zhuinden (props to them!) +class FragmentViewBindingDelegate( + val fragment: Fragment, + val viewBindingFactory: (View) -> T +) : ReadOnlyProperty { + private var _binding: T? = null + + init { + fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner -> + viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + _binding = null + } + }) + } + } + }) + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>): T { + val binding = _binding + if (binding != null) { + return binding + } + + val lifecycle = fragment.viewLifecycleOwner.lifecycle + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { + throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") + } + + return viewBindingFactory(thisRef.requireView()).also { _binding = it } + } +} + +fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = + FragmentViewBindingDelegate(this, viewBindingFactory) \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/widget/CanvasText.kt b/app/src/main/java/com/denizd/textbasic/widget/CanvasText.kt new file mode 100644 index 0000000..2580824 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/widget/CanvasText.kt @@ -0,0 +1,268 @@ +package com.denizd.textbasic.widget + +import android.content.Context +import android.graphics.* +import android.text.StaticLayout +import android.text.TextPaint +import android.util.Log +import com.denizd.textbasic.QuoteStorage +import com.denizd.textbasic.R + +class CanvasText { + + companion object { + fun drawText(context: Context, isPreview: Boolean = false, size: Pair? = null): Bitmap { + + + val previewSize = 20f + + val storage = QuoteStorage(context) + + size?.let { + storage.setSize(size) +// Log.d("ASDF", "${size.first} + ${size.second}") + } + + val widgetSize = storage.getSize() + val outlineSize = storage.getOutlineSize() + + val bmp = if (isPreview) { + Bitmap.createBitmap(1024, 256, Bitmap.Config.ARGB_8888) + } else { +// Log.d("ASDFasdf", "${widgetSize.first} + ${widgetSize.second}") + Bitmap.createBitmap(widgetSize.first * 3, widgetSize.second * 3, Bitmap.Config.ARGB_8888) + } + + val canvas = Canvas(bmp) + + val quote = if (isPreview) { + context.getString(R.string.example_text) + } else if (storage.isOrderRandom()) { + storage.getRandomQuote() + } else { + storage.getNextQuote() + }.ifBlank { context.getString(R.string.info_add_text) } + + val colours = storage.getColours(context = context) + + val scaledTextSize = + context.resources.displayMetrics.scaledDensity * if (/*isPreview*/false) previewSize else { + storage.getTextSize().toFloat() + } + val savedTypeface = storage.getTypeface() + + val widgetGravity = storage.getWidgetGravity() + + val paint = TextPaint().apply { + style = Paint.Style.FILL + color = colours.first + textSize = scaledTextSize + textAlign = when (widgetGravity) { + 0, 3, 6 -> Paint.Align.LEFT + 2, 5, 8 -> Paint.Align.RIGHT + else -> Paint.Align.CENTER + } + + isAntiAlias = true + isSubpixelText = false + typeface = savedTypeface + } + + val staticLayout = StaticLayout.Builder + .obtain(quote, 0, quote.length, paint, if (isPreview) 1024 else widgetSize.first * 3).build() + + val boundsText = Rect() + paint.getTextBounds(quote, 0, quote.length, boundsText) + val (x, y) = calculateWidgetGravity(storage.getWidgetGravity(), bmp, boundsText, isPreview) +// val x = canvas.width / 2 +// val y = ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2)) + + val padding = 16f + val alignment = when (widgetGravity) { + 0 -> { + canvas.translate(boundsText.left.toFloat() + padding, 0f) + Paint.Align.LEFT + } + 1 -> { + canvas.translate(canvas.width / 2f, 0f) + Paint.Align.CENTER + } + 2 -> { + canvas.translate(canvas.width.toFloat() - padding, 0f) + Paint.Align.RIGHT + } + 3 -> { + canvas.translate(boundsText.left.toFloat() + padding, 0f) + Paint.Align.LEFT + } + 5 -> { + canvas.translate(canvas.width.toFloat() - padding, 0f) + Paint.Align.RIGHT + } + 6 -> { + canvas.translate(boundsText.left.toFloat() + padding, bmp.height.toFloat() - staticLayout.height - padding) + Paint.Align.LEFT + } + 7 -> { + canvas.translate(canvas.width / 2f, bmp.height.toFloat() - staticLayout.height - padding) + Paint.Align.CENTER + } + 8 -> { + canvas.translate(canvas.width.toFloat() - padding, bmp.height.toFloat() - staticLayout.height - padding) + Paint.Align.RIGHT + } + else -> { // 4 + canvas.translate(canvas.width / 2f, 0f) + Paint.Align.CENTER + } + } + + when (val bgType = storage.getBackgroundType()) { + 0 -> { // background + val bgPaint = Paint().apply { + style = Paint.Style.FILL + color = colours.second + } + val rounding = 24f + +// val rect = roundedRect( +// boundsText.left + x - padding, +// boundsText.top + y - padding, +// boundsText.right + x + padding, +// boundsText.bottom + y + padding, rounding, rounding +// ) + +// val rect = roundedRect( +// 0f, 0f, boundsText.right.toFloat() / 2, staticLayout.height.toFloat(), +// rounding, rounding +// ) + val rect = when (widgetGravity) { // right + 2, 5, 8 -> roundedRect( + -boundsText.right.toFloat() - 4f, 0f, 0f, staticLayout.height.toFloat(), + rounding, rounding + ) + 1, 4, 7 -> roundedRect( // middle + -(boundsText.width() / 2f), 0f, + boundsText.right.toFloat() / 2f, staticLayout.height.toFloat(), + rounding, rounding + ) + else -> roundedRect( // left + 0f, 0f, boundsText.right.toFloat() + 4f, staticLayout.height.toFloat(), + rounding, rounding + ) + } + + canvas.drawPath(rect, bgPaint) + } + 1, 2 -> { // outline or shadow + val strokePaint = TextPaint().apply { + style = Paint.Style.STROKE + strokeWidth = outlineSize //if (isPreview) previewSize / 1.5f else + color = colours.second + textSize = scaledTextSize + textAlign = alignment + + isAntiAlias = true + isSubpixelText = false + typeface = savedTypeface + + if (bgType == 2) { // blur outline for shadow + maskFilter = BlurMaskFilter(if (outlineSize == 0f) 1f else outlineSize, BlurMaskFilter.Blur.NORMAL) + } + } + val strokeStaticLayout = StaticLayout.Builder + .obtain(quote, 0, quote.length, strokePaint, if (isPreview) 1024 else widgetSize.first * 3).build() + strokeStaticLayout.draw(canvas) +// canvas.drawText(quote, x, y, strokePaint) + } + } + + staticLayout.draw(canvas) +// canvas.drawText(quote, x, y, paint) + return bmp + } + + private fun calculateWidgetGravity(widgetGravity: Int, bitmap: Bitmap, boundsText: Rect, isPreview: Boolean) : Pair { + return if (isPreview) { + Pair((bitmap.width - boundsText.width()) / 2f, (bitmap.height + boundsText.height()) / 2f) + } else when (widgetGravity) { + 0 -> { // top left + Pair(0f + boundsText.left, (boundsText.height() - boundsText.top).toFloat()) + } + 1 -> { // top center + Pair( + (bitmap.width - boundsText.width()) / 2f, + (boundsText.height() - boundsText.top).toFloat() + ) + } + 2 -> { // top right + Pair( + (bitmap.width - (boundsText.right)).toFloat(), + (boundsText.height() - boundsText.top).toFloat() + ) + } + 3 -> { // center left + Pair(0f + boundsText.left, (bitmap.height + boundsText.height()) / 2f) + } + // 4 == else branch + 5 -> { // center right + Pair( + (bitmap.width - (boundsText.right)).toFloat(), + (bitmap.height + boundsText.height()) / 2f + ) + } + 6 -> { // bottom left + Pair(0f + boundsText.left, (bitmap.height - boundsText.bottom).toFloat()) + } + 7 -> { // bottom center + Pair( + (bitmap.width - boundsText.width()) / 2f, + (bitmap.height - boundsText.bottom).toFloat() + ) + } + 8 -> { // bottom right + Pair( + (bitmap.width - (boundsText.right)).toFloat(), + (bitmap.height - boundsText.bottom).toFloat() + ) + } + else -> { // center center LIKE 4 + Pair( + (bitmap.width - boundsText.width()) / 2f, + (bitmap.height + boundsText.height()) / 2f + ) + } + } + } + + fun roundedRect( + left: Float, top: Float, right: Float, bottom: Float, + roundingX: Float, roundingY: Float + ): Path { + var rx = roundingX + var ry = roundingY + val path = Path() + if (rx < 0) rx = 0f + if (ry < 0) ry = 0f + val width = right - left + val height = bottom - top + if (rx > width / 2) rx = width / 2 + if (ry > height / 2) ry = height / 2 + val widthMinusCorners = width - 2 * rx + val heightMinusCorners = height - 2 * ry + path.moveTo(right, top + ry) + path.rQuadTo(0f, -ry, -rx, -ry) //top-right corner + path.rLineTo(-widthMinusCorners, 0f) + path.rQuadTo(-rx, 0f, -rx, ry) //top-left corner + path.rLineTo(0f, heightMinusCorners) + + path.rQuadTo(0f, ry, rx, ry) //bottom-left corner + path.rLineTo(widthMinusCorners, 0f) + path.rQuadTo(rx, 0f, rx, -ry) //bottom-right corner + + path.rLineTo(0f, -heightMinusCorners) + path.close() //Given close, last lineto can be removed. + return path + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/widget/TextCanvasWidget.kt b/app/src/main/java/com/denizd/textbasic/widget/TextCanvasWidget.kt new file mode 100644 index 0000000..d75a516 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/widget/TextCanvasWidget.kt @@ -0,0 +1,68 @@ +package com.denizd.textbasic.widget + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.* +import android.os.Bundle +import android.util.Log +import android.widget.RemoteViews +import com.denizd.textbasic.R +import kotlin.random.Random + +class TextCanvasWidget : AppWidgetProvider() { + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + + // Perform this loop procedure for each App Widget that belongs to this provider + appWidgetIds.forEach { appWidgetId -> + + val remoteViews = configureViews(context, appWidgetIds) + // Tell the AppWidgetManager to perform an update on the current app widget + appWidgetManager.updateAppWidget(appWidgetId, remoteViews) + } + } + + private fun configureViews(context: Context, appWidgetIds: IntArray, size: Pair? = null): RemoteViews = + RemoteViews(context.packageName, R.layout.widget_bitmap).apply { + + val bitmap = CanvasText.drawText(context, size = size) + + setImageViewBitmap(R.id.bitmap_view, bitmap) + + setOnClickPendingIntent( + R.id.bmp_root_layout, + Intent(context, TextCanvasWidget::class.java).let { intent -> + intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) + PendingIntent.getBroadcast( + context, Random.nextInt(), intent, PendingIntent.FLAG_IMMUTABLE + ) + } + ) + } + + override fun onAppWidgetOptionsChanged( + context: Context?, + appWidgetManager: AppWidgetManager?, + appWidgetId: Int, + newOptions: Bundle? + ) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) + + val width = (newOptions?.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) ?: 0) + val height = (newOptions?.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) ?: 0) + +// Log.d("ASDFF", (newOptions?.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)).toString()) + + + context?.let { + val remoteViews = configureViews(context, intArrayOf(appWidgetId), Pair(width, height)) + // Tell the AppWidgetManager to perform an update on the current app widget + appWidgetManager?.updateAppWidget(appWidgetId, remoteViews) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/color/bottom_nav_color.xml b/app/src/main/res/color/bottom_nav_color.xml new file mode 100644 index 0000000..7b79748 --- /dev/null +++ b/app/src/main/res/color/bottom_nav_color.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/add.xml b/app/src/main/res/drawable/add.xml new file mode 100644 index 0000000..eb23254 --- /dev/null +++ b/app/src/main/res/drawable/add.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_bottom.xml b/app/src/main/res/drawable/arrow_bottom.xml new file mode 100644 index 0000000..901daf4 --- /dev/null +++ b/app/src/main/res/drawable/arrow_bottom.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_bottom_left.xml b/app/src/main/res/drawable/arrow_bottom_left.xml new file mode 100644 index 0000000..6f12610 --- /dev/null +++ b/app/src/main/res/drawable/arrow_bottom_left.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_bottom_right.xml b/app/src/main/res/drawable/arrow_bottom_right.xml new file mode 100644 index 0000000..701f3d5 --- /dev/null +++ b/app/src/main/res/drawable/arrow_bottom_right.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_down_small.xml b/app/src/main/res/drawable/arrow_down_small.xml new file mode 100644 index 0000000..9f82205 --- /dev/null +++ b/app/src/main/res/drawable/arrow_down_small.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_left.xml b/app/src/main/res/drawable/arrow_left.xml new file mode 100644 index 0000000..e383687 --- /dev/null +++ b/app/src/main/res/drawable/arrow_left.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_right.xml b/app/src/main/res/drawable/arrow_right.xml new file mode 100644 index 0000000..5a9661d --- /dev/null +++ b/app/src/main/res/drawable/arrow_right.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_top.xml b/app/src/main/res/drawable/arrow_top.xml new file mode 100644 index 0000000..129add2 --- /dev/null +++ b/app/src/main/res/drawable/arrow_top.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_top_left.xml b/app/src/main/res/drawable/arrow_top_left.xml new file mode 100644 index 0000000..5e3792e --- /dev/null +++ b/app/src/main/res/drawable/arrow_top_left.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_top_right.xml b/app/src/main/res/drawable/arrow_top_right.xml new file mode 100644 index 0000000..5893394 --- /dev/null +++ b/app/src/main/res/drawable/arrow_top_right.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/arrow_up_small.xml b/app/src/main/res/drawable/arrow_up_small.xml new file mode 100644 index 0000000..a55069f --- /dev/null +++ b/app/src/main/res/drawable/arrow_up_small.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/book_outlined.xml b/app/src/main/res/drawable/book_outlined.xml new file mode 100644 index 0000000..283b63a --- /dev/null +++ b/app/src/main/res/drawable/book_outlined.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/cloud_download.xml b/app/src/main/res/drawable/cloud_download.xml new file mode 100644 index 0000000..5cf9f5d --- /dev/null +++ b/app/src/main/res/drawable/cloud_download.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/cloud_upload.xml b/app/src/main/res/drawable/cloud_upload.xml new file mode 100644 index 0000000..2896516 --- /dev/null +++ b/app/src/main/res/drawable/cloud_upload.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml new file mode 100644 index 0000000..5fb767e --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/dot.xml b/app/src/main/res/drawable/dot.xml new file mode 100644 index 0000000..d8da4f2 --- /dev/null +++ b/app/src/main/res/drawable/dot.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/hellothere.png b/app/src/main/res/drawable/hellothere.png new file mode 100644 index 0000000..b593648 Binary files /dev/null and b/app/src/main/res/drawable/hellothere.png differ diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/layout_bg.xml b/app/src/main/res/drawable/layout_bg.xml new file mode 100644 index 0000000..daa61a7 --- /dev/null +++ b/app/src/main/res/drawable/layout_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/pen_outlined.xml b/app/src/main/res/drawable/pen_outlined.xml new file mode 100644 index 0000000..5b5fa7a --- /dev/null +++ b/app/src/main/res/drawable/pen_outlined.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/settings_outlined.xml b/app/src/main/res/drawable/settings_outlined.xml new file mode 100644 index 0000000..c624d19 --- /dev/null +++ b/app/src/main/res/drawable/settings_outlined.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/font/mono_bold.ttf b/app/src/main/res/font/mono_bold.ttf new file mode 100644 index 0000000..900fce6 Binary files /dev/null and b/app/src/main/res/font/mono_bold.ttf differ diff --git a/app/src/main/res/font/mono_bold_italic.ttf b/app/src/main/res/font/mono_bold_italic.ttf new file mode 100644 index 0000000..4bfe29a Binary files /dev/null and b/app/src/main/res/font/mono_bold_italic.ttf differ diff --git a/app/src/main/res/font/mono_extralight.ttf b/app/src/main/res/font/mono_extralight.ttf new file mode 100644 index 0000000..d535884 Binary files /dev/null and b/app/src/main/res/font/mono_extralight.ttf differ diff --git a/app/src/main/res/font/mono_extralight_italic.ttf b/app/src/main/res/font/mono_extralight_italic.ttf new file mode 100644 index 0000000..b28960a Binary files /dev/null and b/app/src/main/res/font/mono_extralight_italic.ttf differ diff --git a/app/src/main/res/font/mono_italic.ttf b/app/src/main/res/font/mono_italic.ttf new file mode 100644 index 0000000..4ee4dc4 Binary files /dev/null and b/app/src/main/res/font/mono_italic.ttf differ diff --git a/app/src/main/res/font/mono_light.ttf b/app/src/main/res/font/mono_light.ttf new file mode 100644 index 0000000..276af4c Binary files /dev/null and b/app/src/main/res/font/mono_light.ttf differ diff --git a/app/src/main/res/font/mono_light_italic.ttf b/app/src/main/res/font/mono_light_italic.ttf new file mode 100644 index 0000000..a2801c2 Binary files /dev/null and b/app/src/main/res/font/mono_light_italic.ttf differ diff --git a/app/src/main/res/font/mono_medium.ttf b/app/src/main/res/font/mono_medium.ttf new file mode 100644 index 0000000..8461be7 Binary files /dev/null and b/app/src/main/res/font/mono_medium.ttf differ diff --git a/app/src/main/res/font/mono_medium_italic.ttf b/app/src/main/res/font/mono_medium_italic.ttf new file mode 100644 index 0000000..a3bfaa1 Binary files /dev/null and b/app/src/main/res/font/mono_medium_italic.ttf differ diff --git a/app/src/main/res/font/mono_regular.ttf b/app/src/main/res/font/mono_regular.ttf new file mode 100644 index 0000000..7c4ce36 Binary files /dev/null and b/app/src/main/res/font/mono_regular.ttf differ diff --git a/app/src/main/res/font/mono_semibold.ttf b/app/src/main/res/font/mono_semibold.ttf new file mode 100644 index 0000000..15ee6c6 Binary files /dev/null and b/app/src/main/res/font/mono_semibold.ttf differ diff --git a/app/src/main/res/font/mono_semibold_italic.ttf b/app/src/main/res/font/mono_semibold_italic.ttf new file mode 100644 index 0000000..8e21497 Binary files /dev/null and b/app/src/main/res/font/mono_semibold_italic.ttf differ diff --git a/app/src/main/res/font/mono_thin.ttf b/app/src/main/res/font/mono_thin.ttf new file mode 100644 index 0000000..ee8a3fd Binary files /dev/null and b/app/src/main/res/font/mono_thin.ttf differ diff --git a/app/src/main/res/font/mono_thin_italic.ttf b/app/src/main/res/font/mono_thin_italic.ttf new file mode 100644 index 0000000..40b01e4 Binary files /dev/null and b/app/src/main/res/font/mono_thin_italic.ttf differ 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..e4ee742 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_guide.xml b/app/src/main/res/layout/fragment_guide.xml new file mode 100644 index 0000000..98073a9 --- /dev/null +++ b/app/src/main/res/layout/fragment_guide.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_quote.xml b/app/src/main/res/layout/fragment_quote.xml new file mode 100644 index 0000000..ad45dea --- /dev/null +++ b/app/src/main/res/layout/fragment_quote.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..86747f6 --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/quote_entry.xml b/app/src/main/res/layout/quote_entry.xml new file mode 100644 index 0000000..e563a49 --- /dev/null +++ b/app/src/main/res/layout/quote_entry.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/widget_bitmap.xml b/app/src/main/res/layout/widget_bitmap.xml new file mode 100644 index 0000000..bf885b5 --- /dev/null +++ b/app/src/main/res/layout/widget_bitmap.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/widget_text.xml b/app/src/main/res/layout/widget_text.xml new file mode 100644 index 0000000..2bce134 --- /dev/null +++ b/app/src/main/res/layout/widget_text.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/nav_items.xml b/app/src/main/res/menu/nav_items.xml new file mode 100644 index 0000000..51865a8 --- /dev/null +++ b/app/src/main/res/menu/nav_items.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..8f595ba Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..b993506 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..d52cd4c Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..d945109 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..20da542 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..2863e0c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..4db4bc4 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..d6c4a67 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..44c0b3d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..37ceee3 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6102f44 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..5a37488 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..26e3af4 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a4a31ee Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..2b91c0e Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..63517ba --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,36 @@ + + + Text Basic + Gib etwas ein... + Dies ist ein Beispieltext + Textgröße + Opazität + Dicke + Stärke + Farben umkehren + Zufällige Reihenfolge + Kontrast erhöhen + Füg etwas hinzu! + Daten werden wiederhergestellt... + Deine Daten werden innerhalb der nächsten Stunde gesichert + + Text bearbeiten + Anleitung + Einstellungen + + Position + + Sans-Serif + Serif + Monospace + + Hintergrund + Umriss + Schatten + + Normal + Fett + Kursiv + Fett & kursiv + Text Basic ist eine einfache App, um einen beliebigen Text auf deinem Startbildschirm darzustellen! Füge einfach das Text Basic-Widget auf deinem Startbildschirm hinzu, gib Text in die App ein, und der Text wird auf deinem Bildschirm angezeigt.\n\nWechsle zwischen Texten: In der App kannst du beliebig viele Texte eingeben, indem du den [+]-Knopf drückst und deinen Text in das neu aufgetauchte Feld eingibst. Alle Texte werden automatisch gespeichert, du musst nichts weiteres tun.\nDu kannst zwischen deinen Texten wechseln, indem du auf deinem Startbildschirm auf das Widget klickst. Die Texte werden in der Reihenfolge angezeigt, in der du sie eingegeben hast, du kannst jedoch \"zufällige Reihenfolge\" in den App-Einstellungen aktivieren, um eine willkürliche Reihenfolge zu nutzen.\nHINWEIS: Sollte das Wechseln zwischen Texten nicht klappen, so versuche bitte, das Widget zu vergrößern und wieder zu verkleinern. Danach sollte der Fehler behoben sein.\n\nVerändere den Look: Du kannst verändern, wie der Text aussieht, indem du in die App-Einstellungen schaust – dort findest du Optionen für verschiedene Schriftarten und -größen, du kannst die Position bearbeiten sowie den Textstil, und du kannst einen Hintergrund, Umriss, oder Schatten hinzufügen.\nTipp: Sollte dein Text durch dein Hintergrundbild nicht leicht zu erkennen sein, versuche die Farben umzukehren. + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..f19c78d --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,38 @@ + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..c2817df --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..b6a9845 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,72 @@ + + + #91379e + #91379E + #FFFFFF + #FFD6FD + #36003F + #6B586B + #FFFFFF + #F4DBF1 + #251626 + #82524A + #FFFFFF + #FFDAD4 + #33110C + #BA1A1A + #FFDAD6 + #FFFFFF + #410002 + #FFFBFF + #1E1A1D + #FFFBFF + #1E1A1D + #ECDFE8 + #4D444C + #7F747C + #F7EEF3 + #332F32 + #FAABFF + #000000 + #91379E + #D0C3CC + #000000 + #FAABFF + #570065 + #761A84 + #FFD6FD + #D8BFD5 + #3B2B3C + #534153 + #F4DBF1 + #F6B8AD + #4C251F + #673B34 + #FFDAD4 + #FFB4AB + #93000A + #690005 + #FFDAD6 + #1E1A1D + #E9E0E4 + #1E1A1D + #E9E0E4 + #4D444C + #D0C3CC + #998D96 + #1E1A1D + #E9E0E4 + #91379E + #000000 + #FAABFF + #4D444C + #000000 + + #f3ecf4 + #30262f + + #000000 + #ffffff + #212121 + #f0f0f0 + diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml new file mode 100644 index 0000000..828e162 --- /dev/null +++ b/app/src/main/res/values/dimen.xml @@ -0,0 +1,4 @@ + + + 12dp + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..3e196a2 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #F7ADFC + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..c1a2374 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,46 @@ + + Text Basic + Enter something... + This is an example text + text size + opacity + thickness + intensity + Invert colours + Randomise order + Increase the contrast + add some text! + Restoring your data... + Your data will be backed up within the next hour + + Edit Text + Guide + Settings + + position + + Sans-Serif + Serif + Monospace + + Background + Outline + Shadow + + Normal + Bold + Italics + Bold & Italics + + Text Basic is a simple app for displaying a text on your home screen! Just add the Text Basic widget to your home screen, enter text in the app, and it will show on your home screen. +\n\nCycle through texts: In the app, you can enter as many texts as you like by clicking the [+] button and entering your text into the newly appeared field. All texts are saved automatically, you have to take no further action.\nYou can cycle through your texts by clicking on the widget on your home screen. They will be shown in the order you added them in, but you can turn on "random order" in the app\'s settings to randomise the order. +\nNOTE: if clicking on the widget does not cycle between texts, please try changing the size of the widget and then change it back. This should solve the issue. +\n\nCustomise the look: You can change how your text will look by heading into the app\'s settings – there, you can edit the text appearance by changing the size, font, position, apply formatting options, and even add a background, an outline, or a shadow. +\nTip: if the text is hard to read on your wallpaper, try inverting the colours. + + + Sans-Serif + Serif + Monospace + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..8959ff7 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,40 @@ + + + + diff --git a/app/src/main/res/xml/appwidget_info.xml b/app/src/main/res/xml/appwidget_info.xml new file mode 100644 index 0000000..32c6426 --- /dev/null +++ b/app/src/main/res/xml/appwidget_info.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/test/java/com/denizd/textbasic/ExampleUnitTest.kt b/app/src/test/java/com/denizd/textbasic/ExampleUnitTest.kt new file mode 100644 index 0000000..2cf8888 --- /dev/null +++ b/app/src/test/java/com/denizd/textbasic/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.denizd.textbasic + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/banner.png b/banner.png new file mode 100644 index 0000000..65da272 Binary files /dev/null and b/banner.png differ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..225ff1b --- /dev/null +++ b/build.gradle @@ -0,0 +1,10 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '8.0.1' apply false + id 'com.android.library' version '8.0.1' apply false + id 'org.jetbrains.kotlin.android' version '1.8.20' apply false +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..022338b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,25 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..5070359 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Feb 17 15:50:40 CET 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..103c498 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "Text Basic" +include ':app'