Anul 3 Semestrul 1

This commit is contained in:
2025-02-06 20:33:26 +02:00
parent 0b130ee18c
commit 184f3bd92e
313 changed files with 348499 additions and 0 deletions
+128
View File
@@ -77,3 +77,131 @@ __pycache__/
*.gcno
*.gcda
node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# .NET Core
bin/
obj/
project.lock.json
project.fragment.lock.json
artifacts/
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# Auto-generated files
*.generated.cs
*.generated.ts
# Build results
[Dd]ebug/
[Rr]elease/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# Visual Studio Code
.vscode/
# Rider
.idea/
*.sln.iml
# User-specific files (Mono Auto Generated)
mono_crash.*
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# VS Code directories
.vscode/
.history/
# Local History for Visual Studio
.localhistory/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
/.vs
/WebApi/.vs
/WebApi/.vs/WebApi/CopilotIndices/0.2.1657.32929
/WebApi/.vs/WebApi/FileContentIndex
/WebApi/.vs/WebApi/copilot-chat/6ceaa888/sessions
/WebApi/.vs/ProjectEvaluation
/WebApi/.vs/WebApi/DesignTimeBuild
/WebApi/.vs/WebApi/v17
/WebApi/AIModels
*.onnx
.DS_Store
*.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
devel/
build/
__pycache__/
.env
experiments/
@@ -0,0 +1,22 @@
q0 q1 q2 q3 qf
1 2 3
q0
qf
q0 q0 1
q0 q0 2
q0 q0 3
q0 q1 1
q0 q2 2
q0 q3 3
q1 q1 1
q1 q1 2
q1 q1 3
q1 qf 1
q2 q2 1
q2 q2 2
q2 q2 3
q2 qf 2
q3 q3 1
q3 q3 2
q3 q3 3
q3 qf 3
@@ -0,0 +1,46 @@
class FiniteAutomata:
def __init__(self, file_path):
self.alphabet = set()
self.states = set()
self.transitions = {}
self.start_state = None
self.final_states = set()
self.read_fa(file_path)
def read_fa(self, file_path):
with open(file_path, "r") as file:
self.states = set(file.readline().strip().split())
self.alphabet = set(file.readline().strip().split())
self.start_state = file.readline().strip()
self.final_states = set(file.readline().strip().split())
for line in file:
state1, state2, char = line.strip().split()
if state1 not in self.transitions:
self.transitions[state1] = {}
if char not in self.transitions[state1]:
self.transitions[state1][char] = []
self.transitions[state1][char].append(state2)
def is_accepted(self, word):
current_states = [self.start_state]
for char in word:
new_states = []
for state in current_states:
if state in self.transitions and char in self.transitions[state]:
new_states.extend(self.transitions[state][char])
current_states = new_states
return any(state in self.final_states for state in current_states)
def isNFA(self):
for state in self.transitions:
for char in self.transitions[state]:
if len(self.transitions[state][char]) > 1:
return True
return False
fa = FiniteAutomata("FA.in")
print(fa.isNFA())
print(fa.is_accepted("1312"))
print(fa.is_accepted("12321"))
print(fa.is_accepted(""))
@@ -0,0 +1,161 @@
import re
from collections import defaultdict
from Parser import RecursiveDescentParser
from ParserOutput import TreeNode, ParserOutput
class Grammar:
def __init__(self):
self.nonterminals = set()
self.terminals = set()
self.productions = defaultdict(list)
self.start_symbol = None
def read_from_file(self, filename):
with open(filename, 'r') as file:
lines = file.readlines()
grammar_header = lines[0].strip()
match = re.match(r"G\s*=\s*\(\s*\{([^}]*)\}\s*,\s*\{([^}]*)\}\s*,\s*P\s*,\s*([^)]+)\s*\)", grammar_header)
if not match:
raise ValueError("Invalid grammar format in the first line.")
self.nonterminals = set([x.strip() for x in set(match.group(1).split(', '))])
self.terminals = set([x.strip() for x in set(match.group(2).split(', '))])
self.start_symbol = match.group(3)
# Parse the productions
for line in lines[1:]:
line = line.strip()
if line.startswith("P :"):
line = line[3:].strip()
if "->" in line:
lhs, rhs = line.split('->')
lhs = lhs.strip()
self.nonterminals.add(lhs)
for production in rhs.split('|'):
production = production.strip()
self.productions[lhs].append(production.strip())
def print_nonterminals(self):
print("Nonterminals:", self.nonterminals)
def print_terminals(self):
print("Terminals:", self.terminals)
def print_productions(self):
for lhs, rhs in self.productions.items():
print(f"{lhs} -> {' | '.join(rhs)}")
def get_productions_for(self, nonterminal):
return self.productions.get(nonterminal, [])
def is_cfg(self):
for lhs, rhs_list in self.productions.items():
if lhs not in self.nonterminals:
print(f"Invalid nonterminal: {lhs}")
return False
for rhs in rhs_list:
rhs_symbols = rhs.split()
for symbol in rhs_symbols:
if symbol not in self.terminals and symbol not in self.nonterminals:
print(f"Invalid symbol in RHS: {symbol}")
return False
return True
def menu():
g = Grammar()
#g.read_from_file("g2.txt")
parser = None
while True:
print("1. Read grammar from file")
print("2. Print nonterminals")
print("3. Print terminals")
print("4. Print productions")
print("5. Get productions for a nonterminal")
print("6. Check if the grammar is CFG")
print("7. Parse input string")
print("0. Exit")
input_command = input("Enter your command: ")
match input_command:
case "1":
filename = input("Enter the filename: ")
g.read_from_file(filename)
case "2":
g.print_nonterminals()
case "3":
g.print_terminals()
case "4":
g.print_productions()
case "5":
nonterminal = input("Enter the nonterminal: ")
print(g.get_productions_for(nonterminal))
case "6":
print(g.is_cfg())
case "7":
input_string = input("Enter the input string: ")
parser = RecursiveDescentParser(g, input_string)
if parser.parse():
print(parser.print_parse_tree())
parser.save_parse_tree("out1.txt")
else:
print("The input string is not in the language")
print(parser.stack)
print(parser.parsed_stack)
case "0":
return
case _:
print("Invalid command")
if __name__ == '__main__':
menu()
# g = Grammar()
# g.read_from_file("g1.txt")
# parser = RecursiveDescentParser(g, "a a b")
# parser.expand("S", 0)
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# parser.advance()
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# parser.expand("A", 1)
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# parser.advance()
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# parser.back()
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# parser.another_try("A",1)
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# parser.advance()
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# parser.advance()
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
# print(parser.success())
# print(parser.current_pos)
# print(parser.stack)
# print(parser.parsed_stack)
@@ -0,0 +1,78 @@
import copy
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.next = None
class HashTable:
def __init__(self):
self.capacity = 2
self.noOfElements = 0
self.elementList = [None] * 2
def hash(self, value):
if isinstance(value, int):
return value % self.capacity
sum = 0
for l in value:
sum += ord(l)
return sum % self.capacity
def insert(self, key, value):
if self.noOfElements and (self.capacity // self.noOfElements < 2):
self.resizeAndRehash()
hash_key = self.hash(key)
element = self.elementList[hash_key]
if element is None:
self.elementList[hash_key] = Node(key, value)
self.noOfElements += 1
return
while element.next is not None:
element = element.next
element.next = Node(key, value)
self.noOfElements += 1
def get(self, key):
element = self.elementList[self.hash(key)]
while element is not None:
if element.key == key:
return element.value
element = element.next
return None
def getPosition(self, key):
element = self.elementList[self.hash(key)]
position = 0
while element is not None:
if element.key == key:
return (key,position)
element = element.next
position += 1
return None
def resizeAndRehash(self):
self.capacity *= 2
copyElementList = copy.deepcopy(self.elementList)
self.elementList = [None] * self.capacity
self.noOfElements = 0
for element in copyElementList:
copyElement = copy.deepcopy(element)
while copyElement is not None:
self.insert(copyElement.key, copyElement.value)
copyElement = copyElement.next
def __str__(self):
var = ""
for element in self.elementList:
while element is not None:
var += f"{element.key} -> {element.value}\n"
element = element.next
return var
@@ -0,0 +1,69 @@
class Main{
void entry Main(){
Problem1([3,5,7,2])
Problem2([3,5,7,2])
int[] x = [3,5,7,2]
Problem3(x,[8,4,2,8])
}
int Problem1(int[] numbers){
int max = -1;
int index = 0;
while(index < numbers.lenght){
int number = numbers[index];
if(number > max){
max = number;
}
index = index + 1;
}
return max;
}
int Problem2(int[] numbers){
var prod = 0;
int index = 0;
while(index < numbers.lenght){
int number = numbers[index];
prod *= number;
}
index = index + 1;
return prod;
}
void Problem3(int[] numbers1, int[] numbers2){
while(index < numbers.lenght){
numbers1[index] = numbers1[index] + numbers2[index];
index = index + 1;
}
}
Problem1err(number, power){
return number ** power
}
}
// Key Features of the Language:
// OOP:
// The programming language supports classes and object. Everything written must belong to a class
// Entry Point:
// The entry point of the program is defined by a method with the keyword entry.
// Method Definitions:
// Methods are defined using modifiers such as public and return types like int or var.
// Dynamic Typing and Static Typing:
// var is used for variables where the type is dynamically inferred, similar to languages like Python. This suggests a flexible type system where variables can hold different types based on context. It can however also support static typing
// Loops and Iteration:
// The foreach loop is used to iterate over collections, such as lists. The syntax is familiar to C# or Python-style iteration.
// Lexical Rules:
// Functions are declared with a return type (must be present even if void or dynamic), followed by the function name, and the parameter list. The parameter list can have strong types (like List<int>) or flexible types (like var), but must contain a type. Every statement must be followed by a semi-colon (;), and code is separated into blocks using { and }
@@ -0,0 +1,103 @@
from ParserOutput import ParserOutput, TreeNode
class RecursiveDescentParser:
def __init__(self, grammar, input_string):
self.grammar = grammar
self.input = input_string.split()
self.current_pos = 0
self.stack = [] # Stack for backtracking
self.parsed_stack = [] # Current sequence of symbols being parsed
self.production_sequence = [] # To track applied productions
self.parse_tree = ParserOutput(self.grammar.terminals, self.grammar.nonterminals) # Parse tree output
def expand(self, nonterminal, prod_index=0):
"""Expand a nonterminal using the specified production index."""
if nonterminal in self.grammar.productions:
production = self.grammar.productions[nonterminal][prod_index]
self.stack.append((nonterminal, prod_index, self.current_pos, len(self.parsed_stack))) # Push state
self.production_sequence.append((nonterminal, production)) # Track production
self.parsed_stack = production.split() + self.parsed_stack # Add production to parsed stack
else:
raise ValueError(f"No production for {nonterminal}")
def advance(self):
if self.current_pos < len(self.input) and self.parsed_stack:
next_symbol = self.parsed_stack.pop(0)
if next_symbol == self.input[self.current_pos]:
self.current_pos += 1
return True
else:
self.parsed_stack.insert(0, next_symbol) # Push it back
return False
return False
def back(self):
if self.stack:
# Pop the last saved state
nonterminal, prod_index, prev_pos, stack_size = self.stack.pop()
# Restore the previous position in the input
self.current_pos = prev_pos
# Restore the stack to the state it was before expanding the production
self.parsed_stack = self.parsed_stack[:stack_size]
self.production_sequence.pop()
# Try the next production for the same nonterminal
return self.another_try(nonterminal, prod_index)
# If the stack is empty, backtracking is not possible
return False
def another_try(self, nonterminal, prod_index):
"""Try the next production of the nonterminal."""
prod_list = self.grammar.productions.get(nonterminal, [])
if prod_index + 1 < len(prod_list):
self.expand(nonterminal, prod_index + 1) # Expand using the next production
return True
return False
def success(self):
"""Check if parsing succeeded."""
return self.current_pos == len(self.input) and not self.parsed_stack
def parse(self):
if not self.grammar.start_symbol:
raise ValueError("Grammar must have a start symbol defined.")
self.expand(self.grammar.start_symbol)
while True:
# If parsing succeeds
if self.success():
self.parse_tree.build_tree(self.production_sequence)
return True
# If there's something to parse
if self.parsed_stack:
next_symbol = self.parsed_stack[0]
# If next symbol is a terminal, try to match it
if next_symbol in self.grammar.terminals:
if not self.advance():
# If matching fails, backtrack
if not self.back() and len(self.stack) <= 1:
return False
elif next_symbol in self.grammar.nonterminals:
# Expand nonterminal
self.parsed_stack.pop(0)
self.expand(next_symbol)
else:
# Handle unexpected symbols
print(f"Error: Unexpected symbol {next_symbol}")
return False
else:
if not self.back() and len(self.stack) <= 1:
return False
def print_parse_tree(self):
"""Prints the parse tree to the screen."""
self.parse_tree.print_to_screen()
def save_parse_tree(self, filename):
"""Saves the parse tree representation to a file."""
self.parse_tree.save_to_file(filename)
@@ -0,0 +1,111 @@
import string
class TreeNode:
def __init__(self, value, father=None):
self.value = value # Grammar symbol (terminal/nonterminal)
self.father = father # Parent node
self.sibling = None # Sibling node
self.children = [] # List of children for easy access
def add_child(self, child):
"""Adds a child to the current node."""
if self.children:
# Set sibling for the last child
self.children[-1].sibling = child
self.children.append(child)
def __str__(self):
"""String representation of the node."""
return self.value
class ParserOutput:
def __init__(self, terminal, nonterminal):
self.root = None # Root of the parse tree
self.terminal = terminal
self.nonterminal = nonterminal
def build_tree(self, production_sequence):
"""
Builds the parse tree from a sequence of productions.
:param production_sequence: List of (nonterminal, production) tuples
"""
if not production_sequence:
return None
stack = [] # Stack for constructing the tree
for nonterminal, production in production_sequence:
if not self.root:
# Create the root node
self.root = TreeNode(nonterminal)
stack.append(self.root)
current = stack.pop()
symbols = production.split()
for symbol in symbols:
child = TreeNode(symbol, current)
current.add_child(child)
if symbol in self.nonterminal: # Nonterminal
stack.append(child)
else:
# Create nodes based on production
current = stack.pop()
symbols = production.split()
for symbol in symbols:
child = TreeNode(symbol, current)
current.add_child(child)
if symbol in self.nonterminal: # Nonterminal
stack.append(child)
def transform_representation(self, node=None, level=0):
if node is None:
node = self.root
result = "-" * level + str(node) + "\n"
for child in node.children:
result += self.transform_representation(child, level + 1)
return result
def print_to_screen(self):
print(self.print_table())
def print_table(self):
"""
Prints a table representation of the parse tree.
"""
rows = []
index_map = {}
def traverse(node, index=1, parent_index=None):
current_index = len(rows)
index_map[node] = current_index
# Determine right sibling
sibling_index = None
if node.sibling:
sibling_index = len(rows) + 1 # Right sibling will be the next node added
rows.append((current_index, node.value, parent_index, sibling_index))
for child in node.children:
traverse(child, len(rows), current_index)
traverse(self.root)
# Print the table
s = ""
s += "+-------+-------+--------+---------------+" + "\n"
s += "| Index | Value | Parent | Right Sibling |" + "\n"
s += "+-------+-------+--------+---------------+" + "\n"
for index, value, parent, sibling in rows:
s += f"| {index:<3} | {value:<3} | {parent + 1 if parent is not None else '0':<4} | {sibling if sibling is not None else '0':<11} |" + "\n"
s += "+-------+-------+--------+---------------+" + "\n"
return s
def save_to_file(self, filename):
with open(filename, 'w') as file:
file.write(self.print_table())
@@ -0,0 +1,84 @@
from operator import le
from SymbolTable import SymbolTable
from Hashtable import HashTable
import re
class Scanner:
def __init__(self, tokensPath):
self.tokensPath = tokensPath
self.symbolTable = SymbolTable()
self.indentifierRegex = r"^[a-zA-Z][a-zA-Z0-9]*$"
self.constantRegex = r"^0$|^[1-9][0-9]*$|^-[1-9][0-9]*$"
self.tokens = []
self.pif = {}
self.readTokens()
def readTokens(self):
with open(self.tokensPath, "r") as file:
isKeyword = True
isOperator = False
for line in file:
self.tokens.append(line.strip())
self.tokensRegex = '|'.join(map(re.escape, self.tokens))
def isIdentifier(self, token):
return re.match(self.indentifierRegex, token)
def isConstant(self, token):
return re.match(self.constantRegex, token)
def isToken(self, token):
return token in self.tokens
def scan(self, codePath):
with open(codePath, "r") as file:
identifierCounter = 1
constantCounter = 1
lineCounter = 0
try:
for line in file:
line = line.strip()
tokens = re.findall(self.tokensRegex + r"|\w+", line)
for token in tokens:
if self.isToken(token):
self.pif[token] = -1
elif self.isIdentifier(token):
position = self.symbolTable.getPositionIdentifier(token)
if position is None:
self.symbolTable.addIdentifier(token, identifierCounter)
self.pif["Identifier"] = identifierCounter
identifierCounter += 1
else:
self.pif["Identifier"] = position[1]
elif self.isConstant(token):
position = self.symbolTable.getPositionConstants(token)
if position is None:
self.symbolTable.addConstants(token, constantCounter)
self.pif['Constant'] = constantCounter
constantCounter += 1
else:
self.pif["Constant"] = position[1]
else:
raise Exception(f"Invalid token {token} at line {lineCounter}")
lineCounter += 1
print("Lexically correct")
except Exception as e:
print(e)
return
def main():
scanner = Scanner("tokens.in")
scanner.scan("p1error.in")
#save output to file
with open("PIF.out", "w") as file:
for key in scanner.pif:
file.write(f"{key} -> {scanner.pif[key]}\n")
with open("ST.out", "w") as file:
file.write(str(scanner.symbolTable))
if __name__ == "__main__":
main()
@@ -0,0 +1,40 @@
from Hashtable import HashTable
class SymbolTable:
def __init__(self):
self.symboltableConstants = HashTable()
self.symboltableIdentifier = HashTable()
def addIdentifier(self, identifier, value):
self.symboltableIdentifier.insert(identifier, value)
def getIdentifier(self, identifier):
return self.symboltableIdentifier.get(identifier)
def getPositionIdentifier(self, identifier):
return self.symboltableIdentifier.getPosition(identifier)
def addConstants(self, constants, value):
self.symboltableConstants.insert(constants, value)
def getConstants(self, constants):
return self.symboltableConstants.get(constants)
def getPositionConstants(self, constants):
return self.symboltableConstants.getPosition(constants)
def __str__(self):
var = "Identifier Table\n"
for i in range(self.symboltableIdentifier.capacity):
element = self.symboltableIdentifier.elementList[i]
while element is not None:
var += str(element.key) + " " + str(element.value) + "\n"
element = element.next
var += "Constants Table\n"
for i in range(self.symboltableConstants.capacity):
element = self.symboltableConstants.elementList[i]
while element is not None:
var += str(element.key) + " " + str(element.value) + "\n"
element = element.next
return var
@@ -0,0 +1,4 @@
G = ({S, A}, {a, b}, P, S)
P :
S -> a A
A -> a | b | a A | b A
@@ -0,0 +1,40 @@
G = ({program, statement_list, statement, assignment_stmt, input_stmt, output_stmt, if_stmt, for_stmt, array_decl_stmt, condition, increment_stmt, expression, term, factor, relational_operator, binary_operator, concatenation},{=, (, ), [, ], if, else, for, ARRAY, echo, readline, +, -, *, /, <, <=, ==, >=, !=, ++, --, :, ;, IDENTIFIER, INTEGER_CONST, FLOAT_CONST, STRING_CONST},P, program)
P :
program -> statement_list
statement_list -> statement | statement statement_list
statement -> assignment_stmt | input_stmt | output_stmt | if_stmt | for_stmt | array_decl_stmt
assignment_stmt -> IDENTIFIER = expression
input_stmt -> IDENTIFIER = readline_function ( STRING_CONST )
output_stmt -> echo_function ( STRING_CONST concatenation IDENTIFIER )
if_stmt -> if ( condition ) : statement | if ( condition ) : statement else : statement
for_stmt -> for ( assignment_stmt ; condition ; increment_stmt ) : statement
array_decl_stmt -> IDENTIFIER = ARRAY [ INTEGER_CONST ]
condition -> expression relational_operator expression
increment_stmt -> IDENTIFIER ++ | IDENTIFIER --
expression -> term | term binary_operator expression
term -> factor | factor binary_operator term
factor -> IDENTIFIER | INTEGER_CONST | FLOAT_CONST | STRING_CONST
relational_operator -> < | <= | == | >= | !=
binary_operator -> + | - | * | /
concatenation -> +
echo_function -> echo
readline_function -> readline
@@ -0,0 +1,112 @@
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lang.tab.h"
// Define YYSTYPE structure
extern YYSTYPE yylval;
extern int yydebug; // Declare yydebug
// Token definitions
// File pointers
FILE *yyin, *yyout;
%}
%%
"int"|"char"|"void" { fprintf(yyout, "SIMPLETYPE\n"); return SIMPLETYPE; }
"class" { fprintf(yyout, "CLASS\n"); return CLASS; }
"entry" { fprintf(yyout, "ENTRY\n"); return ENTRY; }
"if" { fprintf(yyout, "IF\n"); return IF; }
"while" { fprintf(yyout, "WHILE\n"); return WHILE; }
"write" { fprintf(yyout, "WRITE\n"); return WRITE; }
"read" { fprintf(yyout, "READ\n"); return READ; }
"return" { fprintf(yyout, "RETURN\n"); return RETURN; }
"{" { fprintf(yyout, "LBRACE\n"); return LBRACE; }
"}" { fprintf(yyout, "RBRACE\n"); return RBRACE; }
"(" { fprintf(yyout, "LPAREN\n"); return LPAREN; }
")" { fprintf(yyout, "RPAREN\n"); return RPAREN; }
"[" { fprintf(yyout, "LBRACKET\n"); return LBRACKET; }
"]" { fprintf(yyout, "RBRACKET\n"); return RBRACKET; }
"+" { fprintf(yyout, "PLUS\n"); return PLUS; }
"-" { fprintf(yyout, "MINUS\n"); return MINUS; }
"*" { fprintf(yyout, "MULT\n"); return MULT; }
"/" { fprintf(yyout, "DIV\n"); return DIV; }
"=" { fprintf(yyout, "ASSIGN\n"); return ASSIGN; }
"==" { fprintf(yyout, "EQ\n"); return EQ; }
"<" { fprintf(yyout, "LT\n"); return LT; }
"<=" { fprintf(yyout, "LE\n"); return LE; }
">" { fprintf(yyout, "GT\n"); return GT; }
">=" { fprintf(yyout, "GE\n"); return GE; }
";" { fprintf(yyout, "SEMICOLON\n"); return SEMICOLON; }
"." { fprintf(yyout, "DOT\n"); return DOT; }
"," { fprintf(yyout, "COMMA\n"); return COMMA; }
[0-9]+ { yylval.intval = atoi(yytext); fprintf(yyout, "CONSTANTEXP %d\n", yylval.intval); return CONSTANTEXP; }
[a-zA-Z_][a-zA-Z0-9_]* { yylval.strval = strdup(yytext); fprintf(yyout, "IDENTIFIER %s\n", yylval.strval); return IDENTIFIER; }
[ \t\n]+ { /* Ignore spaces and tabs */ }
\r\n { /* Increment line count if needed */ }
. { fprintf(yyout, "Unknown character: %s\n", yytext); }
%%
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
return 1;
}
// Open input and output files
yyin = fopen(argv[1], "r");
if (!yyin) {
perror("Failed to open input file");
return 1;
}
yyout = fopen(argv[2], "w");
if (!yyout) {
perror("Failed to open output file");
fclose(yyin);
return 1;
}
int token;
while ((token = yylex()) != 0) {
// Process tokens
}
fclose(yyin);
fclose(yyout);
yyin = fopen(argv[1], "r");
if (!yyin) {
perror("Failed to open input file");
return 1;
}
yyout = stdout;
if (!yyout) {
perror("Failed to open output file");
fclose(yyin);
return 1;
}
// Close files
// printf("%d\n", yylex());
// printf("%d\n", yylex());
// printf("%d\n", yylex());
// printf("%d\n", yylex());
yydebug = 1;
if (yyparse() == 0) {
printf("Parsing successful\n");
} else {
printf("Parsing failed\n");
}
fclose(yyin);
fclose(yyout);
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,103 @@
/* A Bison parser, made by GNU Bison 3.8.2. */
/* Bison interface for Yacc-like parsers in C
Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
under terms of your choice, so long as that work isn't itself a
parser generator using the skeleton or a modified version thereof
as a parser skeleton. Alternatively, if you modify or redistribute
the parser skeleton itself, you may (at your option) remove this
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison. */
/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
especially those whose name start with YY_ or yy_. They are
private implementation details that can be changed or removed. */
#ifndef YY_YY_LANG_TAB_H_INCLUDED
# define YY_YY_LANG_TAB_H_INCLUDED
/* Debug traces. */
#ifndef YYDEBUG
# define YYDEBUG 1
#endif
#if YYDEBUG
extern int yydebug;
#endif
/* Token kinds. */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
enum yytokentype
{
YYEMPTY = -2,
YYEOF = 0, /* "end of file" */
YYerror = 256, /* error */
YYUNDEF = 257, /* "invalid token" */
SIMPLETYPE = 258, /* SIMPLETYPE */
CLASS = 259, /* CLASS */
ENTRY = 260, /* ENTRY */
IF = 261, /* IF */
WHILE = 262, /* WHILE */
WRITE = 263, /* WRITE */
READ = 264, /* READ */
RETURN = 265, /* RETURN */
IDENTIFIER = 266, /* IDENTIFIER */
CONSTANTEXP = 267, /* CONSTANTEXP */
LBRACE = 268, /* LBRACE */
RBRACE = 269, /* RBRACE */
LPAREN = 270, /* LPAREN */
RPAREN = 271, /* RPAREN */
LBRACKET = 272, /* LBRACKET */
RBRACKET = 273, /* RBRACKET */
ASSIGN = 274, /* ASSIGN */
EQ = 275, /* EQ */
LT = 276, /* LT */
LE = 277, /* LE */
GT = 278, /* GT */
GE = 279, /* GE */
SEMICOLON = 280, /* SEMICOLON */
DOT = 281, /* DOT */
PLUS = 282, /* PLUS */
MINUS = 283, /* MINUS */
MULT = 284, /* MULT */
DIV = 285, /* DIV */
COMMA = 286 /* COMMA */
};
typedef enum yytokentype yytoken_kind_t;
#endif
/* Value type. */
typedef struct {
int intval;
char *strval;
} YYSTYPE;
extern YYSTYPE yylval;
int yyparse (void);
#endif /* !YY_YY_LANG_TAB_H_INCLUDED */
@@ -0,0 +1,153 @@
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void yyerror(const char *s);
typedef struct ASTNode {
char *type;
char *value;
struct ASTNode *left;
struct ASTNode *right;
} ASTNode;
ASTNode *createNode(const char *type, const char *value, ASTNode *left, ASTNode *right);
%}
%token SIMPLETYPE CLASS ENTRY IF WHILE WRITE READ RETURN
%token IDENTIFIER
%token CONSTANTEXP
%token LBRACE RBRACE LPAREN RPAREN LBRACKET RBRACKET ASSIGN EQ LT LE GT GE SEMICOLON DOT PLUS MINUS MULT DIV
%token COMMA
%start program
%left PLUS MINUS
%left MULT DIV
%left LT LE GT GE EQ
%%
program:
CLASS IDENTIFIER LBRACE entry_declaration class_body RBRACE
;
class_body:
%empty
| atrib_declaration class_body
| method_declaration class_body
;
atrib_declaration:
SIMPLETYPE IDENTIFIER SEMICOLON
| SIMPLETYPE IDENTIFIER ASSIGN CONSTANTEXP SEMICOLON
;
method_declaration:
SIMPLETYPE IDENTIFIER LPAREN method_params RPAREN LBRACE method_body RBRACE
;
method_params:
%empty
| SIMPLETYPE IDENTIFIER
| SIMPLETYPE IDENTIFIER COMMA method_params
;
method_body:
%empty
| statement_list
;
statement_list:
%empty
| statement statement_list
;
statement:
atrib_statement
| if_statement
| while_statement
| write_statement
| read_statement
| return_statement
;
atrib_statement:
IDENTIFIER ASSIGN expression SEMICOLON
;
if_statement:
IF LPAREN expression RPAREN LBRACE statement_list RBRACE
;
while_statement:
WHILE LPAREN expression RPAREN LBRACE statement_list RBRACE
;
write_statement:
WRITE LPAREN expression RPAREN SEMICOLON
;
read_statement:
READ LPAREN IDENTIFIER RPAREN SEMICOLON
;
return_statement:
RETURN expression SEMICOLON
;
expression:
CONSTANTEXP
| expression PLUS expression
| expression MINUS expression
| expression MULT expression
| expression DIV expression
| expression EQ expression
| expression LT expression
| expression LE expression
| expression GT expression
| expression GE expression
| LPAREN expression RPAREN
| IDENTIFIER
| IDENTIFIER LBRACKET expression RBRACKET
| LBRACKET array_list RBRACKET
| class_method_call
| class_expression
;
class_expression:
IDENTIFIER
| IDENTIFIER DOT class_expression
;
class_method_call:
class_expression LPAREN array_list RPAREN
array_list:
%empty
| expression
| expression COMMA array_list
;
entry_declaration:
SIMPLETYPE ENTRY IDENTIFIER LPAREN RPAREN LBRACE statement_list RBRACE
;
%%
void yyerror(const char *s) {
fprintf(stderr, "Error: %s\n", s);
}
ASTNode *createNode(const char *type, const char *value, ASTNode *left, ASTNode *right) {
ASTNode *node = (ASTNode *)malloc(sizeof(ASTNode));
node->type = strdup(type);
node->value = value ? strdup(value) : NULL;
node->left = left;
node->right = right;
return node;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
#test the symbol table
from SymbolTable import SymbolTable
def main():
symbol_table = SymbolTable()
symbol_table.addIdentifier("a", 0)
symbol_table.addIdentifier("ba", 1)
symbol_table.addIdentifier("ab", 2)
print(symbol_table.getIdentifier("a"))
print(symbol_table.getIdentifier("ba"))
print(symbol_table.getIdentifier("ab"))
print(symbol_table.getPositionIdentifier("a"))
print(symbol_table.getPositionIdentifier("ba"))
print(symbol_table.getPositionIdentifier("ab"))
print(symbol_table.getIdentifier("d"))
print(symbol_table.getPositionIdentifier("d"))
symbol_table.addIdentifier("d", 4)
print(symbol_table.getIdentifier("d"))
print(symbol_table.getPositionIdentifier("d"))
if __name__ == "__main__":
main()
@@ -0,0 +1,16 @@
CLASS
IDENTIFIER Main
LBRACE
SIMPLETYPE
ENTRY
IDENTIFIER Main
LPAREN
RPAREN
LBRACE
WRITE
LPAREN
CONSTANTEXP 1
RPAREN
SEMICOLON
RBRACE
RBRACE
@@ -0,0 +1,5 @@
class Main{
void entry Main(){
write(1);
}
}
@@ -0,0 +1,7 @@
class Main{
Problem1err(number, power){
return 01 ** power
}
}
@@ -0,0 +1,23 @@
class Main{
void entry Main(){
Problem2([3,5,7,2])
}
int Problem2(int[] numbers){
var prod = 0;
int index = 0;
while(index < numbers.lenght){
int number = numbers[index];
prod *= number;
}
index = index + 1;
return prod;
}
void Problem3(int[] numbers1, int[] numbers2){
while(index < numbers.lenght){
numbers1[index] = numbers1[index] + numbers2[index];
index = index + 1;
}
}
}
@@ -0,0 +1,16 @@
class Main{
void entry Main(){
int[] x = [3,5,7,2]
Problem3(x,[8,4,2,8])
}
void Problem3(int[] numbers1, int[] numbers2){
while(index < numbers.lenght){
numbers1[index] = numbers1[index] + numbers2[index];
index = index + 1;
}
}
Problem1err(number, power){
return number ** power
}
}
@@ -0,0 +1 @@
class
@@ -0,0 +1,32 @@
+
-
*
/
[
{
}
(
)
;
]
=
<
>
<=
>=
==
;
,
.
int
char
class
if
while
read
write
entry
return
null
void
@@ -0,0 +1,89 @@
# Gradle files
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar
.gradle/
**/build/
**/out/
**/buildSrc/build/
**/buildSrc/.gradle/
**/local.properties
# Android Studio files
.idea/
*.iml
*.hprof
*.orig
*.log
*.lock
*.pyc
*.swp
*.swo
*.class
*.jar
*.war
*.ear
*.db
*.sqlite
*.apk
*.ap_
*.dex
*.class
*.so
*.dll
*.pdb
*.o
*.a
*.lib
*.nib
*.plist
*.hmap
*.ipa
*.xcuserstate
*.xcuserdatad
*.xcworkspace
*.xcuserdata
*.xcodeproj
*.xcodeproj/project.xcworkspace
*.xcodeproj/xcuserdata
*.xcodeproj/xcuserdata/*.xcuserdatad
*.xcodeproj/xcuserdata/*.xcuserdatad/xcschemes
# built application files
*.apk
*.ap_
# Mac files
.DS_Store
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
# Ignore gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
proguard-project.txt
# Eclipse files
.project
.classpath
.settings/
# Android Studio/IDEA
*.iml
.idea
.kotlin/
node_modules/
@@ -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
@@ -0,0 +1 @@
/build
@@ -0,0 +1,59 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.example.ma_ui_native"
compileSdk = 34
defaultConfig {
applicationId = "com.example.ma_ui_native"
minSdk = 34
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
@@ -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
@@ -0,0 +1,24 @@
package com.example.ma_ui_native
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.example.ma_ui_native", appContext.packageName)
}
}
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MAUINative"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MAUINative">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:exported="true"
android:theme="@style/Theme.MAUINative">
</activity>
</application>
</manifest>
@@ -0,0 +1,15 @@
package com.example.ma_ui_native
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
data class Drug(
var Id: Int = -1,
var Name: String = "",
var Category: String = "",
var NumberOfUnits: Int = 0,
var RetailPrice: Double = 0.0,
var Manufacturer: String = ""
){
}
@@ -0,0 +1,5 @@
package com.example.ma_ui_native;
public class DrugViewModel {
}
@@ -0,0 +1,300 @@
package com.example.ma_ui_native
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.ma_ui_native.ui.theme.MAUINativeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MAUINativeTheme {
val openDialog = remember { mutableStateOf(false) }
val dialogId = remember { mutableStateOf(-1) }
if(openDialog.value){
DeleteDialog(
dialogId.value,
onConfirm = {
Service.DeleteDrug(dialogId.value)
openDialog.value = false
},
onDismiss = {
openDialog.value = false
}
)
}
DrugList({
openDialog.value = true
dialogId.value = it
})
Column(
modifier = Modifier
.padding(48.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.End,
) {
val context = LocalContext.current
FAB(onClick = {
context.startActivity(Intent(context, SecondActivity::class.java))
})
}
}
}
}
}
@Composable
fun FAB(onClick: () -> Unit) {
FloatingActionButton(
onClick = { onClick() },
shape = CircleShape,
contentColor = MaterialTheme.colorScheme.onPrimary,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(Icons.Outlined.Edit, "Floating action button.")
}
}
@Composable
fun DrugList(onDelete: (Int) -> Unit){
LazyColumn(
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.padding(top = 75.5.dp, bottom = 23.5.dp, start = 26.dp, end = 26.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy((10.dp))
){
items(
Service.GetDrugs(),
key = { it.value.Id},
){
DrugDisplay(it.value, onDelete)
}
}
}
@Composable
fun DrugDisplay(drug: Drug, onDelete: (Int) -> Unit){
Box {
Box(
modifier = Modifier
.matchParentSize()
.background(MaterialTheme.colorScheme.surface, RoundedCornerShape((12.dp)))
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(12.dp))
)
Column(
modifier = Modifier
.fillMaxSize()
) {
Row(
modifier = Modifier
.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 7.dp)
.fillMaxWidth()
.height(44.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(drug.Name, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyLarge)
Text(drug.Manufacturer, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium)
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
val context = LocalContext.current
Button(
onClick = { GoToUpdate(drug.Id, context) },
colors = ButtonColors(
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.primary
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
) {
Text("Edit")
}
Button(
onClick = { onDelete(drug.Id) },
colors = ButtonColors(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimary,
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimary
),
) {
Text("Delete")
}
}
}
HorizontalDivider(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 7.dp)
)
Row(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 7.dp)
.shadow(2.dp, shape = RoundedCornerShape(12.dp))
.fillMaxWidth()
.height(44.dp)
.background(MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp))
) {
Box(
modifier = Modifier
.fillMaxHeight()
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp))
.fillMaxWidth(0.38f),
) {
Text("Category:", color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.titleSmall)
}
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.62f),
) {
Text(drug.Category, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium)
}
}
Row(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 7.dp)
.shadow(2.dp, shape = RoundedCornerShape(12.dp))
.fillMaxWidth()
.height(44.dp)
.background(MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp))
) {
Box(
modifier = Modifier
.fillMaxHeight()
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp))
.fillMaxWidth(0.38f),
) {
Text("# of Units:", color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.titleSmall)
}
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.62f),
) {
Text(drug.NumberOfUnits.toString(), color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium)
}
}
Row(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 40.dp)
.shadow(2.dp, shape = RoundedCornerShape(12.dp))
.fillMaxWidth()
.height(44.dp)
.background(MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp))
) {
Box(
modifier = Modifier
.fillMaxHeight()
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp))
.fillMaxWidth(0.38f),
) {
Text("Price:", color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.titleSmall)
}
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.62f),
) {
Text("$%.2f".format(drug.RetailPrice), color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium)
}
}
}
}
}
fun GoToUpdate(id: Int, context: Context){
Log.d("Update", "Update for %d was clicked".format(id))
val bundle = Bundle()
bundle.putInt("id", id)
val intend = Intent(context, SecondActivity::class.java)
intend.putExtras(bundle)
context.startActivity(intend)
}
@Composable
fun DeleteDialog(id: Int, onConfirm: () -> Unit, onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = {
onDismiss()
},
title = {
Text("Delete Drug")
},
text = {
Text("Are you sure you want to delete this drug? This action can not be reversed")
},
confirmButton = {
Button(
onClick = {
onConfirm()
}
) {
Text("Delete")
}
},
dismissButton = {
Button(
onClick = {
onDismiss()
}
) {
Text("Cancel")
}
}
)
}
@@ -0,0 +1,273 @@
package com.example.ma_ui_native
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.example.ma_ui_native.ui.theme.MAUINativeTheme
class SecondActivity : ComponentActivity() {
private var drug = Drug()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(intent.hasExtra("id")) {
val id = intent.getIntExtra("id", -1)
if(id != -1) {
drug = Service.GetDrug(id)
}
}
enableEdgeToEdge()
setContent {
MAUINativeTheme {
Column (
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.padding(top = 63.dp, start = 62.dp, end = 62.dp, bottom = 73.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
)
{
Row(
modifier = Modifier
.padding(bottom = 25.dp, start = 61.dp, end = 61.dp)
.fillMaxWidth()
.height(49.dp)
.border(
1.dp,
color = MaterialTheme.colorScheme.outlineVariant,
shape = RoundedCornerShape(8.dp)
),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Text(if(drug.Id == -1) "Add Drug" else "Update Drug", color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelLarge)
}
Column(
modifier = Modifier
.padding(bottom = 30.dp)
.fillMaxWidth()
) {
val drugName = remember { mutableStateOf(drug.Name) }
TextField(
value = drugName.value,
onValueChange = {
drugName.value = it
drug.Name = it
},
label = {Text("Name")},
singleLine = true,
trailingIcon = {
IconButton(
onClick = {
drugName.value = ""
drug.Name = ""
}
)
{
Icon(
Icons.Filled.Clear,
contentDescription = "Clear",
)
} },
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
val drugManufacturer = remember { mutableStateOf(drug.Manufacturer) }
TextField(
value = drugManufacturer.value,
onValueChange = {
drugManufacturer.value = it
drug.Manufacturer = it
},
label = {Text("Manufacturer")},
singleLine = true,
trailingIcon = {
IconButton(
onClick = {
drugManufacturer.value = ""
drug.Manufacturer = ""
}
)
{
Icon(
Icons.Filled.Clear,
contentDescription = "Clear",
)
} },
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
val drugCategory = remember { mutableStateOf(drug.Category) }
TextField(
value = drugCategory.value,
onValueChange = {
drugCategory.value = it
drug.Category = it
},
label = {Text("Category")},
singleLine = true,
trailingIcon = {
IconButton(
onClick = {
drugCategory.value = ""
drug.Category = ""
}
)
{
Icon(
Icons.Filled.Clear,
contentDescription = "Clear",
)
} },
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
val drugNumberOfUnits = remember { mutableStateOf(drug.NumberOfUnits.toString()) }
if (drugNumberOfUnits.value == "0") {
drugNumberOfUnits.value = ""
}
TextField(
value = drugNumberOfUnits.value,
onValueChange = {
drugNumberOfUnits.value = it
drug.NumberOfUnits = it.toIntOrNull() ?: 0
},
label = {Text("Number of Units")},
singleLine = true,
trailingIcon = {
IconButton(
onClick = {
drugNumberOfUnits.value = ""
drug.NumberOfUnits = 0
}
)
{
Icon(
Icons.Filled.Clear,
contentDescription = "Clear",
)
} },
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
val drugRetailPrice = remember { mutableStateOf(drug.RetailPrice.toString()) }
if (drugRetailPrice.value == "0.0") {
drugRetailPrice.value = ""
}
TextField(
value = drugRetailPrice.value,
onValueChange = {
drugRetailPrice.value = it
drug.RetailPrice = (it.toDoubleOrNull() ?: 0.0)
},
label = {Text("Retail Price")},
singleLine = true,
trailingIcon = {
IconButton(
onClick = {
drugRetailPrice.value = ""
drug.RetailPrice = 0.0
}
)
{
Icon(
Icons.Filled.Clear,
contentDescription = "Clear",
)
} },
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
prefix = { Text("$") },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { finish() },
colors = ButtonColors(
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.primary
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
) {
Text("Cancel")
}
Button(
onClick = {
if(drug.Id == -1) {
Service.AddDrug(drug)
} else {
Service.UpdateDrug(drug)
}
finish()
},
colors = ButtonColors(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimary,
MaterialTheme.colorScheme.onSurfaceVariant,
MaterialTheme.colorScheme.onSurface
),
enabled = drug.Name.isNotEmpty() && drug.Manufacturer.isNotEmpty() && drug.Category.isNotEmpty() && drug.NumberOfUnits > 0 && drug.RetailPrice > 0.0
) {
Text(if(drug.Id == -1) "Add" else "Update")
}
}
}
}
}
}
}
@@ -0,0 +1,49 @@
package com.example.ma_ui_native
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.SnapshotStateList
object Service {
private var nextId = 11
private val DrugRepo = mutableStateListOf<MutableState<Drug>>(
mutableStateOf( Drug(1, "Paracetamol", "Analgesic", 100, 2.5, "GSK")),
mutableStateOf(Drug(2, "Amoxicillin", "Antibiotic", 50, 3.5, "Pfizer")),
mutableStateOf(Drug(3, "Omeprazole", "Antacid", 75, 1.5, "AstraZeneca")),
mutableStateOf(Drug(4, "Atorvastatin", "Anticholesterol", 25, 4.5, "Pfizer")),
mutableStateOf(Drug(5, "Metformin", "Antidiabetic", 30, 2.0, "Merck")),
mutableStateOf(Drug(6, "Amlodipine", "Antihypertensive", 40, 3.0, "Novartis")),
mutableStateOf(Drug(7, "Losartan", "Antihypertensive", 35, 3.5, "Merck")),
mutableStateOf(Drug(8, "Simvastatin", "Anticholesterol", 20, 4.0, "AstraZeneca")),
mutableStateOf(Drug(9, "Salbutamol", "Bronchodilator", 45, 2.5, "GSK")),
mutableStateOf(Drug(10, "Levothyroxine", "Hormone", 60, 1.5, "Novartis"))
)
public fun GetDrugs() : SnapshotStateList<MutableState<Drug>> {
return DrugRepo;
}
public fun GetDrug(id: Int) : Drug {
return DrugRepo.find { it.value.Id == id }?.value ?: Drug()
}
public fun AddDrug(drug: Drug) {
drug.Id = nextId++
DrugRepo.add(mutableStateOf( drug))
}
public fun UpdateDrug(drug: Drug) {
val index = DrugRepo.indexOfFirst { it.value.Id == drug.Id }
if (index != -1) {
DrugRepo[index] = mutableStateOf( drug)
}
}
public fun DeleteDrug(id: Int) {
val index = DrugRepo.indexOfFirst { it.value.Id == id }
if (index != -1) {
DrugRepo.removeAt(index)
}
}
}
@@ -0,0 +1,14 @@
package com.example.ma_ui_native.ui.theme
import androidx.compose.ui.graphics.Color
var Primary = Color(0xFF675496)
var OnPrimary = Color(0xFFFFFFFF)
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
@@ -0,0 +1,58 @@
package com.example.ma_ui_native.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun MAUINativeTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
@@ -0,0 +1,34 @@
package com.example.ma_ui_native.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m336,680 l144,-144 144,144 56,-56 -144,-144 144,-144 -56,-56 -144,144 -144,-144 -56,56 144,144 -144,144 56,56ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z"
android:fillColor="#e8eaed"/>
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">MA-UI-Native</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MAUINative" parent="android:Theme.Material.Light.NoActionBar" />
</resources>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
@@ -0,0 +1,17 @@
package com.example.ma_ui_native
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)
}
}
@@ -0,0 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}
@@ -0,0 +1,23 @@
# 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. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-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
@@ -0,0 +1,32 @@
[versions]
agp = "8.7.1"
kotlin = "2.0.0"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
composeBom = "2024.04.01"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
@@ -0,0 +1,6 @@
#Sun Oct 27 15:55:16 EET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -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
@@ -0,0 +1,23 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MA-UI-Native"
include(":app")
@@ -0,0 +1,39 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
app-example
android/
@@ -0,0 +1,50 @@
# Welcome to your Expo app 👋
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
## Get started
1. Install dependencies
```bash
npm install
```
2. Start the app
```bash
npx expo start
```
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
## Get a fresh project
When you're ready, run:
```bash
npm run reset-project
```
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
## Learn more
To learn more about developing your project with Expo, look at the following resources:
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
## Join the community
Join our community of developers creating universal apps.
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
@@ -0,0 +1,72 @@
{
"expo": {
"name": "MA-NonNative",
"slug": "MA-NonNative",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "myapp",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.danielcujba54.MANonNative"
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/images/splash-icon.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
],
["expo-sqlite",
{
"enableFTS": true,
"useSQLCipher": true,
"android": {
"enableFTS": false,
"useSQLCipher": false
},
"ios": {
"customBuildFlags": ["-DSQLITE_ENABLE_DBSTAT_VTAB=1 -DSQLITE_ENABLE_SNAPSHOT=1"]
}
}
],
[
"expo-build-properties",
{
"android": {
"usesCleartextTraffic": true
}
}
]
],
"experiments": {
"typedRoutes": true
},
"extra": {
"router": {
"origin": false
},
"eas": {
"projectId": "295efd7e-de78-4e54-b327-8eec9a3f9bf0"
}
}
}
}
@@ -0,0 +1,32 @@
import { store } from "@/redux/store";
import { Stack } from "expo-router";
import { Provider } from "react-redux";
import { useMaterial3Theme } from "@pchmn/expo-material3-theme";
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from "react-native-paper";
import { StatusBar, useColorScheme, Text } from "react-native";
import { DataLoader } from "@/components/DataLoader";
export default function RootLayout() {
const colorScheme = useColorScheme();
const {theme} = useMaterial3Theme();
const paperTheme =
colorScheme === 'dark'
? { ...MD3DarkTheme, colors: theme.dark }
: { ...MD3LightTheme, colors: theme.light };
return (
<Provider store={store}>
<DataLoader />
<PaperProvider theme={paperTheme}>
<StatusBar
hidden={true}
/>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="add" options={{ headerShown: false }} />
<Stack.Screen name="update/[id]" options={{ headerShown: false }} />
</Stack>
</PaperProvider>
</Provider>
);
}
@@ -0,0 +1,14 @@
import DrugList from "@/components/DrugList";
import Form from "@/components/Form";
import { Link } from "expo-router";
import { Dimensions, Text, View } from "react-native";
import { useTheme } from "react-native-paper";
export default function Add() {
const theme = useTheme();
return (
<View style={{width: "100%", backgroundColor:theme.colors.surface, height: Dimensions.get('window').height}}>
<Form />
</View>
);
}
@@ -0,0 +1,15 @@
import DrugList from "@/components/DrugList";
import { Dimensions, Text, View } from "react-native";
import { useTheme } from "react-native-paper";
export default function Index() {
const theme = useTheme();
return (
<View style={{width: "100%", backgroundColor:theme.colors.surface, height: Dimensions.get('window').height}}>
<DrugList />
</View>
);
}
@@ -0,0 +1,14 @@
import Form from "@/components/Form";
import { Link, useLocalSearchParams } from "expo-router";
import { Dimensions, Text, View } from "react-native";
import { useTheme } from "react-native-paper";
export default function Update() {
const { id } = useLocalSearchParams() as { id: string };
const theme = useTheme();
return (
<View style={{width: "100%", backgroundColor:theme.colors.surface, height: Dimensions.get('window').height}}>
<Form drugId={parseInt(id)} />
</View>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,38 @@
import { RootState } from "@/redux/store";
import { useState, useEffect } from "react";
import { Snackbar } from "react-native-paper";
import { useDispatch, useSelector } from "react-redux";
export const ConnectionNotification = () => {
const dispatch = useDispatch();
const status = useSelector((state: RootState) => state.status);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (!status.isConnected || !status.isOnline) {
setVisible(true);
}
else{
setVisible(false);
}
}, [status.isConnected, status.isOnline]);
const onDismiss = () => {
setVisible(false);
};
return (
<Snackbar
wrapperStyle={{ top: 50, left: "50%", position: 'absolute', transform: [{ translateX: "-42%" }], zIndex: 9999}}
visible={visible}
onDismiss={onDismiss}
style={{ zIndex: 9999, }}
action={{
label: 'Dismiss',
onPress: () => {
setVisible(false);
},
}}
duration={60000}
>
No internet connection
</Snackbar>
);
}
@@ -0,0 +1,17 @@
import { initDB } from "@/database/localdb";
import { useEffect } from "react";
import { View } from "react-native";
import { useDispatch } from "react-redux";
export const DataLoader = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'drug/initialize' });
dispatch({ type: 'drug/fetch' });
};
fetchData();
}, [dispatch]);
return (<View></View>);
};
@@ -0,0 +1,31 @@
import { StyleSheet } from "react-native";
import React from "react";
import { Button, Dialog, MD3Theme, Portal, useTheme, Text } from "react-native-paper";
import { useNavigation } from "@react-navigation/native";
export default function DeletePopUp(props: {drugId: number, onCancel: () => void, onDelete: () => void}) {
const theme = useTheme();
const style = styles(theme);
const navigation = useNavigation<any>(); //TODO: types...
return (
<Portal>
<Dialog
visible={true}
>
<Dialog.Title>Delete Drug</Dialog.Title>
<Dialog.Content>
<Text variant="bodyMedium">Are you sure you want to delete this drug? This action can not be reversed</Text>
</Dialog.Content>
<Dialog.Actions>
<Button contentStyle={{paddingHorizontal: 8}} mode="contained" onPress={props.onCancel}>Cancel</Button>
<Button contentStyle={{paddingHorizontal: 8}} mode="contained" onPress={props.onDelete}>Delete</Button>
</Dialog.Actions>
</Dialog>
</Portal>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
})
@@ -0,0 +1,204 @@
import { Drug } from "@/redux/features/drug/drug";
import { View, Text, StyleSheet } from "react-native";
import { Button, Divider, MD3Theme, useTheme } from "react-native-paper";
import { useSelector } from "react-redux";
import { RootState } from "@/redux/store";
export default function DrugDisplay(props: {drug: Drug, onUpdate: (drugId: number) => void, onDelete: (drugId: number) => void}) {
const theme = useTheme();
const style = styles(theme);
const status = useSelector((state: RootState) => state.status);
return (
<View className="box" style={style.outerbox}>
<View className="box" style={style.boxBackground} >
</View>
<View className="column" style={style.boxForeground}>
<View style={style.rowHeadlineMargin}>
<View className="row" style={style.rowHeadline}>
<View className="column" style={style.column}>
<Text style={style.nameText}>{props.drug.name}</Text>
<Text style={style.manufacturerText}>{props.drug.manufacturer}</Text>
</View>
<View className="row" style={style.rowCTA}>
<Button disabled={!status.isConnected || !status.isOnline} contentStyle={{paddingHorizontal: 8}} textColor={theme.colors.primary} buttonColor={theme.colors.surface} style={style.editButton} onPress={() => props.onUpdate(props.drug.id)}>Edit</Button>
<Button contentStyle={{paddingHorizontal: 8}} textColor={theme.colors.onPrimary} buttonColor={theme.colors.primary} onPress={() => props.onDelete(props.drug.id)} >Delete</Button>
</View>
</View>
</View>
<Divider style={style.horizontalDivider} />
<View style={style.chipMargin}>
<View className="row" style={style.chip}>
<View className="box" style={style.labelChip}>
<Text style={style.label}>Category:</Text>
</View>
<View className="box" style={style.valueChip}>
<Text style={style.value}>{props.drug.category}</Text>
</View>
</View>
</View>
<View style={style.chipMargin}>
<View className="row" style={style.chip}>
<View className="box" style={style.labelChip}>
<Text style={style.label}># of Units:</Text>
</View>
<View className="box" style={style.valueChip}>
<Text style={style.value}>{props.drug.numberOfUnits.toFixed(0)}</Text>
</View>
</View>
</View>
<View style={{...style.chipMargin, ...style.lastChipMargin}}>
<View className="row" style={style.chip}>
<View className="box" style={style.labelChip}>
<Text style={style.label}>Price:</Text>
</View>
<View className="box" style={style.valueChip}>
<Text style={style.value}>${props.drug.price.toFixed(2)}</Text>
</View>
</View>
</View>
</View>
</View>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
boxBackground: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: theme.colors.surface,
borderRadius: 12,
borderColor: theme.colors.outlineVariant,
borderWidth: 1,
zIndex: 10,
},
boxForeground: {
position: "relative",
display: "flex",
flexDirection: "column",
top: 0,
left: 0,
zIndex: 20,
},
outerbox: {
position: "relative",
width: "100%",
marginBottom: 8
},
rowHeadlineMargin:{
marginTop: 16,
marginRight: 16,
marginBottom: 7,
marginLeft: 16,
},
rowHeadline: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
height: 44,
},
column:{
display: "flex",
flexDirection: "column",
},
rowCTA: {
display: "flex",
flexDirection: "row",
gap: 8,
},
nameText:{
color: theme.colors.onSurface,
fontFamily: theme.fonts.bodyLarge.fontFamily,
fontSize: theme.fonts.bodyLarge.fontSize,
lineHeight: theme.fonts.bodyLarge.lineHeight,
letterSpacing: theme.fonts.bodyLarge.letterSpacing,
fontWeight: theme.fonts.bodyLarge.fontWeight,
fontStyle: theme.fonts.bodyLarge.fontStyle
},
manufacturerText:{
color: theme.colors.onSurfaceVariant,
fontFamily: theme.fonts.bodyMedium.fontFamily,
fontSize: theme.fonts.bodyMedium.fontSize,
lineHeight: theme.fonts.bodyMedium.lineHeight,
letterSpacing: theme.fonts.bodyMedium.letterSpacing,
fontWeight: theme.fonts.bodyMedium.fontWeight,
fontStyle: theme.fonts.bodyMedium.fontStyle
},
editButton:{
borderWidth: 1,
borderColor: theme.colors.outline,
},
horizontalDivider:{
marginRight: 16,
marginBottom: 16,
marginLeft: 7,
},
chipMargin:{
marginLeft: 16,
marginRight: 16,
marginBottom: 7,
},
chip: {
display: "flex",
flexDirection: "row",
shadowRadius: 12, //TODO: shadow wack
shadowOffset: { width: 2, height: 2 },
width: "100%" ,
height: 44,
backgroundColor: theme.colors.surfaceDisabled,
borderRadius: 12
},
lastChipMargin: {
marginBottom: 40
},
labelChip: {
height: "100%",
width: "38%",
backgroundColor: theme.colors.primary,
borderTopLeftRadius: 12,
borderBottomLeftRadius: 12,
display: "flex",
alignItems: "center",
justifyContent: "center"
},
label: {
color: theme.colors.onPrimary,
textAlign: "center",
fontFamily: theme.fonts.titleSmall.fontFamily,
fontSize: theme.fonts.titleSmall.fontSize,
lineHeight: theme.fonts.titleSmall.lineHeight,
letterSpacing: theme.fonts.titleSmall.letterSpacing,
fontWeight: theme.fonts.titleSmall.fontWeight,
fontStyle: theme.fonts.titleSmall.fontStyle
},
valueChip: {
height: "100%",
width: "62%",
display: "flex",
alignItems: "center",
justifyContent: "center"
},
value: {
color: theme.colors.onSurfaceVariant,
textAlign: "center",
fontFamily: theme.fonts.bodyMedium.fontFamily,
fontSize: theme.fonts.bodyMedium.fontSize,
lineHeight: theme.fonts.bodyMedium.lineHeight,
letterSpacing: theme.fonts.bodyMedium.letterSpacing,
fontWeight: theme.fonts.bodyMedium.fontWeight,
fontStyle: theme.fonts.bodyMedium.fontStyle
},
});
@@ -0,0 +1,87 @@
import { Drug } from "@/redux/features/drug/drug";
import { View, StyleSheet, FlatList, Dimensions } from "react-native";
import { RootState, AppDispatch } from "@/redux/store";
import { useSelector, useDispatch } from "react-redux";
import React, { useState } from "react";
import DrugDisplay from "./DrugDisplay";
import { FAB, MD3Theme, useTheme } from "react-native-paper";
import { useNavigation } from "@react-navigation/native";
import DeletePopUp from "./DeletePopUp";
import { drugSlice, removeDrug } from "@/redux/features/drug/drugSlice";
import { ConnectionNotification } from "./ConnectionNotification";
import { ErrorNotification } from "./ErrorNotification";
export default function DrugList() {
const drugs: Drug[] = useSelector((state: RootState) => state.drug.drugList);
const dispatch: AppDispatch = useDispatch();
const theme = useTheme();
const style = styles(theme);
const navigation = useNavigation<any>(); //TODO: types...
const [deleteIndex, useDeleteIndex] = useState(-1)
return (
<View style={style.listMargin}>
<ConnectionNotification />
{/* <ErrorNotification /> */}
<FAB
color={theme.colors.onPrimary}
style={style.fab}
icon="pencil-outline"
mode="elevated"
onPress={() => {
navigation.navigate("add");
}}
/>
{deleteIndex != -1 ?
<DeletePopUp
drugId={deleteIndex}
onCancel = {() => {
useDeleteIndex(-1)
}}
onDelete = {() => {
dispatch({type: "drug/remove", payload: deleteIndex})
useDeleteIndex(-1)
}}
/> : null}
<FlatList style={style.list}
data={drugs}
renderItem={({item})=><DrugDisplay
key={item.id}
drug={item}
onUpdate={(drugId: number) => {
navigation.navigate("update/[id]", {id: item.id});
}}
onDelete={(drugId: number) => {
useDeleteIndex(drugId)
}} />}
keyExtractor={item => item.id.toString()}
showsHorizontalScrollIndicator={false}
/>
</View>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
listMargin:{
paddingTop: 75.5,
paddingBottom: 23.5,
paddingLeft: 26,
paddingRight: 26,
height: Dimensions.get('window').height,
flex: 1
},
list: {
backgroundColor: theme.colors.surface,
height: "100%"
},
fab: {
position: 'absolute',
margin: 48,
right: 0,
bottom: 0,
zIndex: 30,
borderRadius: 30,
backgroundColor: theme.colors.primary
}
})
@@ -0,0 +1,39 @@
import { RootState } from "@/redux/store";
import { useState, useEffect } from "react";
import { View } from "react-native";
import { Snackbar } from "react-native-paper";
import { useDispatch, useSelector } from "react-redux";
export const ErrorNotification = () => {
const dispatch = useDispatch();
const status = useSelector((state: RootState) => state.errorStatus);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (status.status) {
setVisible(true);
}
else{
setVisible(false);
}
}, [status.message]);
const onDismiss = () => {
dispatch({type: "errorStatus/setErrorStatus", payload: {status: false, message: ""}})
};
return (
<View style={{ zIndex: 9999, flex: 1, justifyContent: 'center', position: 'absolute', width: '100%', bottom: 0, left: "50%", transform: [{ translateX: "-42%" }]}}>
<Snackbar
visible={visible}
onDismiss={onDismiss}
style={{ zIndex: 9999}}
action={{
label: 'Dismiss',
onPress: () => {
setVisible(false);
},
}}
>
{status.message}
</Snackbar>
</View>
);
}
@@ -0,0 +1,202 @@
import { Drug } from "@/redux/features/drug/drug";
import { AppDispatch, RootState } from "@/redux/store";
import { useNavigation } from "@react-navigation/native";
import { useEffect, useState } from "react";
import { Dimensions, Text, View, StyleSheet } from "react-native";
import { Button, HelperText, MD3Theme, TextInput, useTheme } from "react-native-paper";
import { useDispatch, useSelector } from "react-redux";
export default function Form(props: {drugId?: number}) {
const [drug, setDrug] = useState<Drug>({
id: -1,
name: "",
manufacturer: "",
category: "",
price: 0,
numberOfUnits: 0
});
const [price , setPrice] = useState("0");
const updateDrug = useSelector((state: RootState) => state.drug.drugList.find(drug => drug.id === props.drugId));
const dispatch: AppDispatch = useDispatch();
const theme = useTheme();
const style = styles(theme);
const navigation = useNavigation<any>(); //TODO: types...
useEffect(() => {
if(props.drugId){
if(updateDrug){
setDrug(updateDrug);
setPrice(updateDrug.price.toFixed(2));
}
}
}, [updateDrug]);
const onSubmit = () => {
if(props.drugId){
dispatch({type: "drug/update", payload: drug});
}
else{
dispatch({type: "drug/add", payload: drug});
}
}
const isDisabled = () => {
return drug.name === "" || drug.manufacturer === "" || drug.category === "" || drug.price === 0 || drug.numberOfUnits === 0;
}
return (
<View style={style.mainMargin}>
<View style={style.column}>
<View style={style.chipMargin}>
<View style={style.chip}>
<Text style={style.chipLabel}>{props.drugId ? "Update Drug" : "Add Drug"}</Text>
</View>
</View>
<View style={style.fieldList}>
<View style={style.inputWrapper}>
<TextInput
label="Name"
value={drug?.name}
onChangeText={(text) => {
setDrug({...drug, name: text} as Drug)
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, name: ""} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Manufacturer"
value={drug?.manufacturer}
onChangeText={(text) => {
setDrug({...drug, manufacturer: text} as Drug)
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, manufacturer: ""} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Category"
value={drug?.category}
onChangeText={(text) => {
setDrug({...drug, category: text} as Drug)
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, category: ""} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Number of Units"
value={drug?.numberOfUnits.toString()}
keyboardType="numeric"
onChangeText={(text) => {
if(text === ""){
setDrug({...drug, numberOfUnits: 0} as Drug)
}
else{
setDrug({...drug, numberOfUnits: parseInt(text)} as Drug)
}
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, numberOfUnits: 0} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Price"
value={price}
keyboardType="numeric"
onChangeText={(text) => {
setPrice(text);
if(text === "" || isNaN(Number(text)) || text[text.length - 1] === '.'){
setDrug({...drug, price: 0} as Drug)
}
else{
setPrice(parseFloat(text).toString());
setDrug({...drug, price: parseFloat(text)} as Drug)
}
}}
right={<TextInput.Icon icon="close" onPress={() => {setDrug({...drug, price: 0} as Drug); setPrice("")}}/>}
left={<TextInput.Affix text="$ " />}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
</View>
<View style={style.buttonRow}>
<Button mode="outlined" onPress={() => navigation.navigate("index")}>Cancel</Button>
<Button mode="contained"
textColor={theme.colors.onPrimary}
buttonColor={theme.colors.primary}
onPress={() => {
if(!isDisabled())
{
onSubmit();
navigation.navigate("index")
}
}}>{props.drugId ? "Update" : "Add"}</Button>
</View>
</View>
</View>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
mainMargin:{
paddingTop: 63,
paddingBottom: 62,
paddingLeft: 62,
paddingRight: 73,
height: Dimensions.get('window').height,
flex: 1
},
column: {
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.colors.surface,
},
chipMargin: {
paddingBottom: 25,
paddingLeft: 61,
paddingRight: 61,
},
chip: {
height: 49,
width: "100%",
borderRadius: 8,
borderWidth: 1,
borderColor: theme.colors.outlineVariant,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
},
chipLabel: {
color: theme.colors.onSurface,
textAlign: "center",
fontFamily: theme.fonts.labelLarge.fontFamily,
fontSize: theme.fonts.labelLarge.fontSize,
lineHeight: theme.fonts.labelLarge.lineHeight,
letterSpacing: theme.fonts.labelLarge.letterSpacing,
fontWeight: theme.fonts.labelLarge.fontWeight,
fontStyle: theme.fonts.labelLarge.fontStyle
},
fieldList: {
display: 'flex',
flexDirection: 'column',
marginBottom: 30,
width: "100%"
},
buttonRow: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
gap: 8
},
inputWrapper: {
marginBottom: 16,
width: "100%"
}
})
@@ -0,0 +1,3 @@
export const SERVER_IP = "192.168.0.45" as const;
export const SERVER_PORT = 3000 as const;
export const WS_PORT = 8080 as const;
@@ -0,0 +1,124 @@
import { Drug } from '@/redux/features/drug/drug';
import { openDatabaseAsync, SQLiteDatabase } from 'expo-sqlite'
export const getDBConnection = async () => {
return await openDatabaseAsync("drug.db");
};
export type TempDrug = {
id: number,
type: number,
drug?: Drug
}
export const createTable = async (db: SQLiteDatabase) => {
const exists = await db.getAllAsync(`SELECT name FROM sqlite_master WHERE type='table' AND name='Drug';`);
if (exists.length > 0) {
return;
}
const query = `CREATE TABLE IF NOT EXISTS Drug (
id INTEGER PRIMARY KEY,
name TEXT,
category TEXT,
price REAL,
numberOfUnits INTEGER,
manufacturer TEXT
);`;
await db.runAsync(query);
const tempTable = `CREATE TABLE IF NOT EXISTS TempDrug (
id INTEGER,
type INTEGER);`;
await db.runAsync(tempTable);
};
export const insertDrug = async (db: SQLiteDatabase, drug: Drug) => {
const query = `INSERT INTO Drug (${drug.id !== -1 ? "id," : ""} name, category, price, numberOfUnits, manufacturer) VALUES (${drug.id !== -1 ? "?," : ""} ?, ?, ?, ?, ?);`;
const values = [drug.name, drug.category, drug.price, drug.numberOfUnits, drug.manufacturer];
if (drug.id !== -1) {
values.unshift(drug.id);
}
const response = await db.runAsync(query, values);
console.log("Inserted into local db");
return response.lastInsertRowId;
}
export const getDrugs = async (db: SQLiteDatabase) => {
const query = `SELECT * FROM Drug;`;
const response = await db.getAllAsync(query);
const drugs: Drug[] = [];
for (let i = 0; i < response.length; i++) {
drugs.push(response[i] as Drug);
}
return drugs;
}
export const getDrug = async (db: SQLiteDatabase, id: number) => {
const query = `SELECT * FROM Drug WHERE id = ?;`;
const values = [id];
const response = await db.getFirstAsync(query, values);
return response as Drug;
}
export const editDrug = async (db: SQLiteDatabase, drug: Drug) => {
const query = `UPDATE Drug SET name = ?, category = ?, price = ?, numberOfUnits = ?, manufacturer = ? WHERE id = ?;`;
const values = [drug.name, drug.category, drug.price, drug.numberOfUnits, drug.manufacturer, drug.id];
await db.runAsync(query, values);
}
export const deleteDrug = async (db: SQLiteDatabase, id: number) => {
const query = `DELETE FROM Drug WHERE id = ?;`;
const values = [id];
await db.runAsync(query, values);
console.log("Deleted from local db");
}
export const rehydrateLocalDB = async (drugs: Drug[]) => {
const db = await getDBConnection();
await db.runAsync(`DELETE FROM Drug;`);
for (let i = 0; i < drugs.length; i++) {
await insertDrug(db, drugs[i]);
}
}
export const initDB = async () => {
const db = await getDBConnection();
await createTable(db);
return db;
}
export const insertTempDrug = async (db: SQLiteDatabase, tempDrug: TempDrug) => {
console.log("Inserting temp drug");
console.log(tempDrug);
const query = `INSERT INTO TempDrug (id, type) VALUES (?, ?);`;
const values = [tempDrug.id, tempDrug.type];
await db.runAsync(query, values);
}
export const getTempDrugs = async (db: SQLiteDatabase) => {
const query = `SELECT Drug.*, TempDrug.id, TempDrug.type FROM TempDrug LEFT JOIN Drug ON TempDrug.id = Drug.id;`;
const response = await db.getAllAsync(query);
const tempDrugs: TempDrug[] = [];
for (let i = 0; i < response.length; i++) {
const responseDrug = response[i] as any;
const tempDrug = {
id: responseDrug.id,
type: responseDrug.type,
drug: responseDrug.name ? {
id: responseDrug.id,
name: responseDrug.name,
category: responseDrug.category,
price: responseDrug.price,
numberOfUnits: responseDrug.numberOfUnits,
manufacturer: responseDrug.manufacturer
}
: undefined
}
tempDrugs.push(tempDrug);
}
return tempDrugs;
}
export const deleteTempDrug = async (db: SQLiteDatabase) => {
const query = `DELETE FROM TempDrug`;
await db.runAsync(query);
}
@@ -0,0 +1,58 @@
import { Drug } from "@/redux/features/drug/drug"
import { SERVER_IP, SERVER_PORT } from "@/constants/Constants"
import axios, { Axios, AxiosResponse } from "axios"
import { TempDrug } from "./localdb";
export const insertDrugServer = async (drug: Drug) => {
console.log(`Inserting drug ${drug}`);
const response: AxiosResponse<Drug> = await axios.post(`http://${SERVER_IP}:${SERVER_PORT}/drug`, drug);
if (response.status === 201) {
return response.data;
}
throw new Error("Failed to insert drug");
}
export const getDrugsServer = async () => {
const response: AxiosResponse<Drug[]> = await axios.get(`http://${SERVER_IP}:${SERVER_PORT}/drug`);
if (response.status === 200) {
return response.data;
}
throw new Error("Failed to fetch drugs");
}
export const editDrugServer = async (drug: Drug) => {
console.log(`Editing drug ${drug}`);
const response: AxiosResponse<Drug> = await axios.put(`http://${SERVER_IP}:${SERVER_PORT}/drug?id=${drug.id}`, drug);
if (response.status === 200) {
return response.data;
}
throw new Error("Failed to edit drug");
}
export const deleteDrugServer = async (id: number) => {
console.log(`Deleting drug with id ${id}`);
const response: AxiosResponse = await axios.delete(`http://${SERVER_IP}:${SERVER_PORT}/drug?id=${id}`);
if (response.status === 204) {
return;
}
throw new Error("Failed to delete drug");
}
export const syncOfflineData = async (offlineData: TempDrug[]) => {
const idCounts: Record<number, number> = offlineData.reduce((acc, item) => {
acc[item.id] = (acc[item.id] || 0) + 1;
return acc;
}, {} as Record<number, number>);
const filteredArray: TempDrug[] = offlineData.filter(item => idCounts[item.id] !== 2);
const tasks = [];
for (const item of filteredArray) {
if (item.type === 1) {
tasks.push(insertDrugServer(item.drug!));
} else {
tasks.push(deleteDrugServer(item.id));
}
}
await Promise.all(tasks);
}
@@ -0,0 +1,15 @@
import { SERVER_IP, WS_PORT } from "@/constants/Constants";
import ReconnectingWebSocket from 'reconnecting-websocket';
import WS from 'ws';
const ws = new ReconnectingWebSocket(`ws://${SERVER_IP}:${WS_PORT}`, [], {
WebSocket: WS.WebSocket,
connectionTimeout: 1000,
startClosed: true
});
const getSocket = () => {
return ws;
}
export default getSocket;
@@ -0,0 +1,22 @@
{
"build": {
"preview": {
"android": {
"buildType": "apk"
}
},
"preview2": {
"android": {
"gradleCommand": ":app:assembleRelease"
}
},
"preview3": {
"developmentClient": true
},
"preview4": {
"distribution": "internal"
},
"production": {}
}
}
@@ -0,0 +1,53 @@
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
import { Drug } from '@/redux/features/drug/drug';
import { deleteDrug, editDrug, getDBConnection, getDrugs, insertDrug, insertTempDrug } from '@/database/localdb';
import { setErrorStatus } from '@/redux/features/errorstatus/errorStatusSlice';
export function* addDrug(action: { type: string, payload: Drug }): Generator<any, void, any> {
try {
const db = yield call(getDBConnection);
const id = yield call(insertDrug, db, action.payload);
yield call(insertTempDrug, db, { id: id, type: 1 });
const { id: _, ...payloadWithoutId } = action.payload;
yield put({ type: "drug/addDrug", payload: { id: id, ...payloadWithoutId } });
} catch (e) {
console.error(e);
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
}
}
export function* removeDrug(action: { type: string, payload: number }): Generator<any, void, any> {
try {
const db = yield call(getDBConnection);
yield call(deleteDrug, db, action.payload);
yield call(insertTempDrug, db, { id: action.payload, type: 2 });
yield put({ type: "drug/removeDrug", payload: action.payload });
} catch (e) {
console.error(e);
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
}
}
export function* updateDrug(action: { type: string, payload: Drug }): Generator<any, void, any> {
try {
const db = yield call(getDBConnection);
yield call(editDrug, db, action.payload);
yield put({ type: "drug/updateDrug", payload: action.payload });
} catch (e) {
console.error(e);
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
}
}
export function* fetchDrugs(): Generator<any, void, any> {
try {
const db = yield call(getDBConnection);
const drugs = yield call(getDrugs, db);
yield put({ type: "drug/fetchDrugs", payload: drugs });
} catch (e) {
console.error(e);
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
}
}
@@ -0,0 +1,64 @@
import { initDB } from "@/database/localdb";
import { call, put, select, takeEvery } from "redux-saga/effects";
import { SERVER_IP, SERVER_PORT } from "@/constants/Constants";
import axios, { AxiosResponse } from "axios"
import { addDrug, fetchDrugs, removeDrug, updateDrug } from "./localdbSaga";
import { fetch, NetInfoState } from "@react-native-community/netinfo";
import { setConnected, setOnline } from "@/redux/features/status/statusSlice";
import { Status } from "@/redux/features/status/status";
import { addDrugServer, checkConnection, fetchDrugsServer, removeDrugServer, updateDrugServer, webSocketSaga } from "./serverSaga";
function* initialize() {
yield call(initDB);
yield call(checkConnection);
console.log("Initialized");
yield put({ type: 'drug/fetch' });
yield webSocketSaga();
}
function* fetchOfflineOrOnline() {
const status: Status = yield select(state => state.status);
if (status.isConnected && status.isOnline) {
yield call(fetchDrugsServer);
} else {
yield call(fetchDrugs);
}
}
function* addDrugOfflineOrOnline(action: { type: string, payload: any }) {
const status: Status = yield select(state => state.status);
if (status.isConnected && status.isOnline) {
yield call(addDrugServer, action);
} else {
yield call(addDrug, action);
}
}
function* removeDrugOfflineOrOnline(action: { type: string, payload: number }) {
const status: Status = yield select(state => state.status);
if (status.isConnected && status.isOnline) {
yield call(removeDrugServer, action);
} else {
yield call(removeDrug, action);
}
}
function* updateDrugOfflineOrOnline(action: { type: string, payload: any }) {
const status: Status = yield select(state => state.status);
if (status.isConnected && status.isOnline) {
yield call(updateDrugServer, action);
} else {
yield call(updateDrug, action);
}
}
export function* rootSaga() {
yield takeEvery('drug/initialize', initialize);
yield takeEvery('drug/fetch', fetchOfflineOrOnline);
yield takeEvery('drug/add', addDrugOfflineOrOnline);
yield takeEvery('drug/remove', removeDrugOfflineOrOnline);
yield takeEvery('drug/update', updateDrugOfflineOrOnline);
yield takeEvery('status/checkConnection', checkConnection);
}
@@ -0,0 +1,161 @@
import { deleteDrugServer, editDrugServer, getDrugsServer, insertDrugServer, syncOfflineData } from "@/database/serverdb";
import { Drug } from "@/redux/features/drug/drug";
import { all, call, put, retry, select, take } from "redux-saga/effects";
import { deleteDrug, deleteTempDrug, editDrug, getDBConnection, getTempDrugs, insertDrug, rehydrateLocalDB, TempDrug } from "@/database/localdb";
import { SQLiteDatabase } from "expo-sqlite";
import { Status } from "@/redux/features/status/status";
import { SERVER_IP, SERVER_PORT } from "@/constants/Constants";
import { setOnline, setConnected } from "@/redux/features/status/statusSlice";
import { NetInfoState, fetch } from "@react-native-community/netinfo";
import axios, { AxiosResponse } from "axios";
import { io, Socket } from "socket.io-client";
import { store } from "@/redux/store";
import { EventChannel, eventChannel } from "redux-saga";
import { PayloadAction } from "@reduxjs/toolkit";
import getSocket from "@/database/socket";
import { setErrorStatus } from "@/redux/features/errorstatus/errorStatusSlice";
import ReconnectingWebSocket from "reconnecting-websocket";
export function* fetchDrugsServer() {
try {
const drugs: Drug[] = yield call(() => getDrugsServer());
console.log(drugs);
yield call(rehydrateLocalDB, drugs);
yield put({ type: "drug/fetchDrugs", payload: drugs });
} catch (error) {
console.log(error);
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
}
}
export function* addDrugServer(action: { type: string, payload: Drug }) {
try {
const drug: Drug = yield call(() => insertDrugServer(action.payload));
const db: SQLiteDatabase = yield call(getDBConnection);
console.log(drug);
yield call(() => insertDrug(db, drug));
yield put({ type: "drug/addDrug", payload: drug });
}
catch (error) {
console.log(error);
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
}
}
export function* removeDrugServer(action: { type: string, payload: number }) {
try {
yield call(() => deleteDrugServer(action.payload));
const db: SQLiteDatabase = yield call(getDBConnection);
yield call(() => deleteDrug(db, action.payload));
yield put({ type: "drug/removeDrug", payload: action.payload });
}
catch (error) {
console.log(error);
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
}
}
export function* updateDrugServer(action: { type: string, payload: Drug }) {
try {
yield call(() => editDrugServer(action.payload));
const db: SQLiteDatabase = yield call(getDBConnection);
yield call(() => editDrug(db, action.payload));
yield put({ type: "drug/updateDrug", payload: action.payload });
}
catch (error) {
console.log(error);
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
}
}
export function* checkConnection() {
try {
yield put(setOnline(false));
yield put(setConnected(false));
const internetConnection: NetInfoState = yield call(fetch);
if (!internetConnection.isConnected) {
console.error("No internet connection");
return;
}
yield put(setOnline(true));
console.log("Internet connection available");
const response: AxiosResponse = yield call(axios.get, `http://${SERVER_IP}:${SERVER_PORT}/`, { timeout: 5000, validateStatus: () => true });
if (!(response.status === 200)) {
console.error("Connection to server failed");
return;
}
console.log("Connection to server successful");
yield put(setConnected(true));
} catch (e: any) {
console.error(e);
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
}
}
export function webSocketChannel() {
const socket = getSocket();
return eventChannel((emit) => {
console.log("Websocket saga started");
socket.onopen = () => {
console.log("Connected to WebSocket server!");
emit({ type: "connect" });
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received message:", data);
emit({ type: data.event, payload: data.data });
}
socket.onclose = () => {
console.log("Disconnected from server.");
emit({ type: "disconnect" });
};
return () => {
socket.close();
};
});
}
export function* webSocketSaga() {
const channel: EventChannel<PayloadAction> = yield call(webSocketChannel);
const socket: ReconnectingWebSocket = yield call(getSocket);
socket.reconnect();
while (true) {
const action: PayloadAction<Drug | { id: number } | void> = yield take(channel);
console.log(action);
const db: SQLiteDatabase = yield call(getDBConnection);
switch (action.type) {
case "connect":
console.log("Connected to server via WebSocket");
yield put({ type: "status/checkConnection" });
const tempData: TempDrug[] = yield call(getTempDrugs, db);
yield call(() => syncOfflineData(tempData));
yield call(() => fetchDrugsServer());
yield call(deleteTempDrug, db);
break;
case "itemAdded":
yield call(() => insertDrug(db, action.payload as Drug));
yield put({ type: "drug/addDrug", payload: action.payload as Drug });
break;
case "itemUpdated":
yield call(() => editDrug(db, action.payload as Drug));
yield put({ type: "drug/updateDrug", payload: action.payload as Drug });
break;
case "itemDeleted":
yield call(() => deleteDrug(db, (action.payload as { id: number }).id));
yield put({ type: "drug/removeDrug", payload: (action.payload as { id: number }).id });
break;
case "disconnect":
console.log("Disconnected from server. Reconnecting...");
yield put({ type: "status/checkConnection" });
break;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
{
"name": "ma-nonnative",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"test": "jest --watchAll",
"lint": "expo lint"
},
"jest": {
"preset": "jest-expo"
},
"dependencies": {
"@expo/vector-icons": "^14.0.2",
"@pchmn/expo-material3-theme": "^1.3.2",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/bottom-tabs": "^7.0.0",
"@react-navigation/native": "^7.0.0",
"@reduxjs/toolkit": "^2.3.0",
"axios": "^1.7.9",
"expo": "~52.0.11",
"expo-blur": "~14.0.1",
"expo-build-properties": "^0.13.2",
"expo-constants": "~17.0.3",
"expo-dev-client": "~5.0.6",
"expo-font": "~13.0.1",
"expo-haptics": "~14.0.0",
"expo-linking": "~7.0.3",
"expo-router": "~4.0.9",
"expo-splash-screen": "~0.29.13",
"expo-sqlite": "~15.0.3",
"expo-status-bar": "~2.0.0",
"expo-symbols": "~0.2.0",
"expo-system-ui": "~4.0.4",
"expo-web-browser": "~14.0.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-native": "0.76.3",
"react-native-gesture-handler": "~2.20.2",
"react-native-paper": "^5.12.5",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "^4.0.0",
"react-native-web": "~0.19.13",
"react-native-webview": "13.12.2",
"react-redux": "^9.1.2",
"reconnecting-websocket": "^4.4.0",
"redux-saga": "^1.3.0",
"socket.io-client": "^4.8.1",
"ws": "^8.18.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/jest": "^29.5.12",
"@types/react": "~18.3.12",
"@types/react-native-sqlite-storage": "^6.0.5",
"@types/react-test-renderer": "^18.3.0",
"@types/ws": "^8.5.14",
"jest": "^29.2.1",
"jest-expo": "~52.0.2",
"react-test-renderer": "18.3.1",
"typescript": "^5.3.3"
},
"private": true,
"expo": {
"doctor": {
"reactNativeDirectoryCheck": {
"exclude": [
"@pchmn/expo-material3-theme",
"@reduxjs/toolkit",
"react-redux"
]
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More